mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 02:55:42 +05:00
Blob upload using POST
This commit is contained in:
parent
84dd369c86
commit
de46da0e7a
@ -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});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -19,6 +19,7 @@ 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;
|
||||
@ -26,6 +27,8 @@ 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; }
|
||||
@ -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;
|
||||
@ -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,
|
||||
@ -772,6 +775,51 @@ 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());
|
||||
@ -779,6 +827,7 @@ 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
|
||||
@ -822,7 +871,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
|
||||
|
||||
@ -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;
|
||||
@ -275,39 +277,37 @@ var upload = function (Env, safeKey, content, cb) {
|
||||
|
||||
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?
|
||||
// reject it, just in case
|
||||
return cb('NOT_READY');
|
||||
}
|
||||
|
||||
if (session.currentUploadSize > session.pendingUploadSize) {
|
||||
return cb('E_OVER_LIMIT');
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (!session.blobstage) {
|
||||
makeFileStream(stagePath, function (e, stream) {
|
||||
if (!stream) { return void cb(e); }
|
||||
|
||||
var blobstage = session.blobstage = stream;
|
||||
blobstage.write(dec);
|
||||
session.currentUploadSize += len;
|
||||
cb(void 0, dec.length);
|
||||
//Env.incrementBytesWritten(len);
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
session.blobstage.write(dec);
|
||||
session.currentUploadSize += len;
|
||||
cb(void 0, dec.length);
|
||||
//Env.incrementBytesWritten(len);
|
||||
}
|
||||
});
|
||||
};
|
||||
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) {
|
||||
@ -370,6 +370,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);
|
||||
});
|
||||
});
|
||||
@ -448,6 +452,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);
|
||||
@ -739,6 +746,16 @@ BlobStore.create = function (config, _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));
|
||||
|
||||
@ -484,18 +484,6 @@ const factory = (Sortify, UserObject, ProxyManager,
|
||||
});
|
||||
};
|
||||
|
||||
Store.uploadChunk = function (clientId, data, cb) {
|
||||
var s = getStore(data.teamId);
|
||||
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
|
||||
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
|
||||
s.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
|
||||
cb({
|
||||
error: e,
|
||||
msg: msg
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var initTempRpc = (clientId, cb) => {
|
||||
if (store.rpc) { return void cb(store.rpc); }
|
||||
var kp = Crypto.Nacl.sign.keyPair();
|
||||
|
||||
@ -22,7 +22,6 @@ const factory = AStore => {
|
||||
GET_PIN_LIMIT: Store.getPinLimit,
|
||||
CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
|
||||
REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
|
||||
UPLOAD_CHUNK: Store.uploadChunk,
|
||||
UPLOAD_COMPLETE: Store.uploadComplete,
|
||||
UPLOAD_STATUS: Store.uploadStatus,
|
||||
UPLOAD_CANCEL: Store.uploadCancel,
|
||||
|
||||
@ -636,13 +636,6 @@ 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); }
|
||||
cb(null, obj);
|
||||
});
|
||||
};
|
||||
|
||||
// ANON RPC
|
||||
|
||||
// SFRAME: talk to anon_rpc from the iframe
|
||||
|
||||
@ -3,15 +3,18 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/api/config',
|
||||
'/file/file-crypto.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/outer/http-command.js',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
'/components/nthen/index.js',
|
||||
], function (FileCrypto, Hash, Util, Cache, nThen) {
|
||||
], function (ApiConfig, FileCrypto, Hash, Util, Cache, ServerCommand, Crypto, nThen) {
|
||||
var module = {};
|
||||
|
||||
module.uploadU8 =function (common, data, cb) {
|
||||
module.uploadU8 = function (common, data, cb) {
|
||||
var teamId = data.teamId;
|
||||
var u8 = data.u8;
|
||||
var metadata = data.metadata;
|
||||
@ -27,13 +30,43 @@ define([
|
||||
|
||||
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
|
||||
|
||||
var sendChunk = function (box, cb) {
|
||||
var enc = Util.encodeBase64(box);
|
||||
common.uploadChunk(teamId, enc, function (e, msg) {
|
||||
cb(e, msg);
|
||||
});
|
||||
let uploadUrl = '/upload-blob';
|
||||
let size = 0;
|
||||
if (ApiConfig.fileHost) {
|
||||
const origin = new URL(ApiConfig.fileHost).origin;
|
||||
uploadUrl = origin + uploadUrl;
|
||||
}
|
||||
var sendChunk = function (box, cb) {
|
||||
const enc = Util.encodeBase64(box);
|
||||
size += enc.length;
|
||||
|
||||
const c = Util.decodeUTF8(cookie);
|
||||
const sig_str = window.nacl.sign(c, keys.secretKey);
|
||||
const sig = Util.encodeBase64(sig_str);
|
||||
|
||||
const body = {
|
||||
chunk: enc,
|
||||
sig: sig,
|
||||
edPublic: keys.edPublic
|
||||
};
|
||||
|
||||
fetch(uploadUrl, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
}).then(res => res.json())
|
||||
.then(json => {
|
||||
if (json?.error) {
|
||||
return cb(json.error);
|
||||
}
|
||||
cookie = json.cookie;
|
||||
cb();
|
||||
}).catch(cb);
|
||||
};
|
||||
|
||||
let keys, cookie;
|
||||
var actual = 0;
|
||||
var encryptedArr = [];
|
||||
var again = function (err, box) {
|
||||
@ -69,6 +102,27 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
const startUpload = () => {
|
||||
common.getAccessKeys(arr => {
|
||||
const myKeys = arr.find(obj => {
|
||||
return (!obj.id && !teamId) || +obj.id === +teamId;
|
||||
});
|
||||
if (!myKeys) { return void onError('NO_KEYS'); }
|
||||
keys = {
|
||||
edPublic: myKeys.edPublic,
|
||||
publicKey: Util.decodeBase64(myKeys.edPublic),
|
||||
secretKey: Util.decodeBase64(myKeys.edPrivate)
|
||||
};
|
||||
ServerCommand(keys, {
|
||||
command: 'UPLOAD_COOKIE',
|
||||
}, (err, data) => {
|
||||
cookie = data?.cookie;
|
||||
if (err || !cookie) { return void onError(err || 'NOCOOKIE'); }
|
||||
next(again);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
common.uploadStatus(teamId, estimate, function (e, pending) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
@ -83,11 +137,11 @@ define([
|
||||
if (e) {
|
||||
return void console.error(e);
|
||||
}
|
||||
next(again);
|
||||
startUpload();
|
||||
});
|
||||
});
|
||||
}
|
||||
next(again);
|
||||
startUpload();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
2
www/common/worker.bundle.min.js
vendored
2
www/common/worker.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user