Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions lib/challenge-commands/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,28 @@ removeBlock.complete = function (Env, body, cb) {
Block.removeLoginBlock(Env, publicKey, reason, edPublic, cb);
};

// Get an upload cookie
// Get a cookie allowing you to upload to the blobstage of your user
const uploadCookie = Commands.UPLOAD_COOKIE = function (Env, body, cb) {
const { publicKey } = body;

// they must provide a valid public key
if (publicKey && typeof(publicKey) === "string"
&& publicKey.length === 44) {
return cb();
}

cb("INVALID_KEY");
};

uploadCookie.complete = function (Env, body, cb) {
const { publicKey } = body;

const safeKey = Util.escapeKeyCharacters(publicKey);
Env.blobStore.uploadCookie(safeKey, (err, cookie) => {
if (err) { return void cb(err); }
cb(void 0, {cookie});
});
};


2 changes: 1 addition & 1 deletion lib/commands/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Upload.status = function (Env, safeKey, filesize, _cb) { // FIXME FILES
};

Upload.upload = function (Env, safeKey, chunk, cb) {
Env.blobStore.upload(safeKey, chunk, cb);
Env.blobStore.uploadWs(safeKey, chunk, cb);
};

Upload.cancel = function (Env, safeKey, arg, cb) {
Expand Down
1 change: 1 addition & 0 deletions lib/http-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const NOAUTH = require("./challenge-commands/base.js");
COMMANDS.MFA_CHECK = NOAUTH.MFA_CHECK;
COMMANDS.WRITE_BLOCK = NOAUTH.WRITE_BLOCK; // Account creation + password change
COMMANDS.REMOVE_BLOCK = NOAUTH.REMOVE_BLOCK;
COMMANDS.UPLOAD_COOKIE = NOAUTH.UPLOAD_COOKIE;

const TOTP = require("./challenge-commands/totp.js");
COMMANDS.TOTP_SETUP = TOTP.TOTP_SETUP;
Expand Down
56 changes: 54 additions & 2 deletions lib/http-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const plugins = require("./plugin-manager");
const gzipStatic = require('connect-gzip-static');
const CPCrypto = require('./crypto');

const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;

let SSOUtils = plugins.SSO && plugins.SSO.utils;

var Env = JSON.parse(process.env.Env);
let blobStore;
let cpcrypto;
Env.plugins = plugins;
const response = Util.response(function (errLabel, info) {
if (!Env.Log) { return; }
Expand Down Expand Up @@ -70,6 +73,7 @@ const EVENTS = {};
EVENTS.ENV_UPDATE = function (data /*, cb */) {
try {
Env = JSON.parse(data);
Env.blobStore = blobStore;
Env.Log = Log;
Env.plugins = plugins;
Env.sendMessage = sendMessage;
Expand Down Expand Up @@ -263,7 +267,6 @@ app.use('/ssoauth', (req, res, next) => {
next();
});


app.use('/blob', function (req, res, next) {
/* Head requests are used to check the size of a blob.
Clients can configure a maximum size to download automatically,
Expand Down Expand Up @@ -774,13 +777,59 @@ app.get('/api/logo', function (req, res) {
});
});

app.post('/upload-blob', Express.json({limit:"500kb"}), (req, res) => {
const { chunk, sig, edPublic } = req.body;
if (!cpcrypto) {
return void res.status(500).send({error: 'NOCRYPTO'});
}

const forbidden = reason => {
return void res.status(403).send({error: reason});
};

try {
// Check signature
const sigu8 = Util.decodeBase64(sig);
const vkey = Util.decodeBase64(edPublic);
const ok = cpcrypto.open(sigu8, vkey);
if (!ok) { return forbidden('INVALID_KEY'); }
const cookie = Util.encodeUTF8(sigu8.subarray(64));
// Check cookie
const safeKey = Util.escapeKeyCharacters(edPublic);
Env.blobStore.checkUploadCookie(safeKey, value => {
if (value !== cookie) {
return forbidden('INVALID_COOKIE');
}
// Upload chunk
Env.blobStore.upload(safeKey, chunk, (err) => {
if (err) {
return res.status(500).send({error: err});
}
// Get new cookie
Env.blobStore.uploadCookie(safeKey, (err, _c) => {
if (err) {
return res.status(500).send({error: err});
}
res.status(200).send({
cookie: _c
});
});
});
});

} catch (e) {
return void res.status(500).send({error: e.message});
}
});

// This endpoint handles authenticated RPCs over HTTP
// via an interactive challenge-response protocol
app.use(Express.json());
app.post('/api/auth', function (req, res, next) {
AuthCommands.handle(Env, req, res, next);
});


app.use(function (req, res /*, next */) {
if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) {
// ignore common 404s
Expand Down Expand Up @@ -824,7 +873,10 @@ nThen(function (w) {
getSession: function () {},
}, w(function (err, blob) {
if (err) { return; }
Env.blobStore = blob;
Env.blobStore = blobStore = blob;
}));
CPCrypto.init(w(function (err, crypto) {
cpcrypto = crypto;
}));
}).nThen(function () {
// TODO inform the parent process that this worker is ready
Expand Down
68 changes: 65 additions & 3 deletions lib/storage/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var BlobStore = module.exports;
var nThen = require("nthen");
var Semaphore = require("saferphore");
var Util = require("../common-util");
const Crypto = require('crypto');

const PERMISSIVE = 511;

const readFileBin = require("../stream-file").readFileBin;
Expand Down Expand Up @@ -270,15 +272,17 @@ var readBlobMetadata = function (env, blobId, handler, _cb) {

/********** METHODS **************/

var upload = function (Env, safeKey, content, cb) {
var uploadWs = function (Env, safeKey, content, cb) {
var dec;

try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }

var len = dec.length;

var session = Env.getSession(safeKey);

/*
if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.pendingUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading?
Expand All @@ -289,6 +293,7 @@ var upload = function (Env, safeKey, content, cb) {
if (session.currentUploadSize > session.pendingUploadSize) {
return cb('E_OVER_LIMIT');
}
*/

var stagePath = makeStagePath(Env, safeKey);

Expand All @@ -300,15 +305,50 @@ var upload = function (Env, safeKey, content, cb) {
blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
});
} else {
session.blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
}
};
var upload = function (Env, safeKey, content, cb) {
var dec;

try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }

var path = makeStagePath(Env, safeKey);
Fs.appendFile(path, dec, cb);
};
const getRandomCookie = function () {
return Crypto.randomBytes(16).toString('hex');
};
var uploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';
const cookie = getRandomCookie();

Fse.mkdirp(Path.dirname(cookiePath), PERMISSIVE, function (err) {
if (err && err.code !== 'EEXIST') { return void cb(err); }
Fs.writeFile(cookiePath, cookie, err => {
cb(err, cookie);
});
});
};
var checkUploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';

Fs.readFile(cookiePath, function (err, content) {
if (err) { return void cb(); }
let expireTime = +new Date() - (5*60*1000);
Fs.stat(cookiePath, function (err, stats) {
if (stats.mtime < expireTime) { return void cb(); }
cb(content.toString('utf8'));
});
});
};

var closeBlobstage = function (Env, safeKey) {
var session = Env.getSession(safeKey);
Expand Down Expand Up @@ -370,6 +410,10 @@ var upload_complete = function (Env, safeKey, id, cb) {
// FIXME we could just move and handle the EEXISTS instead of the above block
Fse.move(oldPath, newPath, function (e) {
if (e) { return void cb('RENAME_ERR'); }

// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});

cb(void 0, id);
});
});
Expand Down Expand Up @@ -448,6 +492,9 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
// otherwise it worked...
}));
}).nThen(function () {
// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});

// clean up their session when you're done
// call back with the blob id...
cb(void 0, id);
Expand Down Expand Up @@ -734,11 +781,26 @@ BlobStore.create = function (config, _cb) {
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
isFile(makeStagePath(Env, safeKey), cb);
},
uploadWs: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadWs(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
upload: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
upload(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
uploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},
checkUploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
checkUploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},

cancel: function (safeKey, fileSize, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
Expand Down
6 changes: 5 additions & 1 deletion src/worker/async-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ const factory = (Sortify, UserObject, ProxyManager,
var initTempRpc = (clientId, cb) => {
if (store.rpc) { return void cb(store.rpc); }
var kp = Crypto.Nacl.sign.keyPair();
var keys = {
var keys = store.tempKeys = {
edPublic: Util.encodeBase64(kp.publicKey),
edPrivate: Util.encodeBase64(kp.secretKey)
};
Expand Down Expand Up @@ -2707,6 +2707,10 @@ const factory = (Sortify, UserObject, ProxyManager,
if (obj?.error) {
Feedback.send("NO_DRIVE_ERROR", true);
}
if (!!data.neverDrive) {
// Send temp RPC keys to the browser to allow upload
obj.tempKeys = store?.tempKeys;
}
cb(obj);
}, !!data.neverDrive);
}
Expand Down
11 changes: 10 additions & 1 deletion www/common/cryptpad-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ define([
}
};

const Env = {};

// Upgrade and donate URLs duplicated in pages.js
var origin = encodeURIComponent(window.location.hostname);
var common = window.Cryptpad = {
Expand Down Expand Up @@ -79,6 +81,12 @@ define([
common.getAccessKeys = function (cb) {
var keys = [];
nThen(function (waitFor) {
// Not logged in? check for temp RPC keys
if (!LocalStore.isLoggedIn() && Env?.returned?.tempKeys) {
keys.push(Env.returned.tempKeys);
return;
}

// Push account keys
postMessage("GET", {
key: ['edPrivate'],
Expand Down Expand Up @@ -631,7 +639,7 @@ define([

common.uploadChunk = function (teamId, data, cb) {
postMessage("UPLOAD_CHUNK", {teamId: teamId, chunk: data}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
Expand Down Expand Up @@ -2736,6 +2744,7 @@ define([

console.log('Posting CONNECT');
postMessage('CONNECT', cfg, function (data) {
Env.returned = data;
// FIXME data should always exist
// this indicates a false condition in sharedWorker
// got here via a reference error:
Expand Down
Loading