mirror of https://github.com/xwiki-labs/cryptpad
Merge branch 'modern-users' into staging
This commit is contained in:
commit
57d0738f32
|
@ -14,6 +14,7 @@ data
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
pins/
|
pins/
|
||||||
blob/
|
blob/
|
||||||
|
block/
|
||||||
blobstage/
|
blobstage/
|
||||||
block/
|
block/
|
||||||
privileged.conf
|
privileged.conf
|
||||||
|
|
|
@ -211,6 +211,11 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
taskPath: './tasks',
|
taskPath: './tasks',
|
||||||
|
|
||||||
|
/* if you would like users' authenticated blocks to be stored in
|
||||||
|
a custom location, change the path below:
|
||||||
|
*/
|
||||||
|
blockPath: './block',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* By default, CryptPad also contacts our accounts server once a day to check for changes in
|
* By default, CryptPad also contacts our accounts server once a day to check for changes in
|
||||||
* the people who have accounts. This check-in will also send the version of your CryptPad
|
* the people who have accounts. This check-in will also send the version of your CryptPad
|
||||||
|
|
|
@ -12,17 +12,22 @@ define([
|
||||||
'/common/common-feedback.js',
|
'/common/common-feedback.js',
|
||||||
'/common/outer/local-store.js',
|
'/common/outer/local-store.js',
|
||||||
'/customize/messages.js',
|
'/customize/messages.js',
|
||||||
|
'/bower_components/nthen/index.js',
|
||||||
|
'/common/outer/login-block.js',
|
||||||
|
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
|
'/bower_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) {
|
Feedback, LocalStore, Messages, nThen, Block) {
|
||||||
var Exports = {
|
var Exports = {
|
||||||
Cred: Cred,
|
Cred: Cred,
|
||||||
|
// this is depended on by non-customizable files
|
||||||
|
// be careful when modifying login.js
|
||||||
|
requiredBytes: 192,
|
||||||
};
|
};
|
||||||
|
|
||||||
var Nacl = window.nacl;
|
var Nacl = window.nacl;
|
||||||
var allocateBytes = function (bytes) {
|
var allocateBytes = Exports.allocateBytes = function (bytes) {
|
||||||
var dispense = Cred.dispenser(bytes);
|
var dispense = Cred.dispenser(bytes);
|
||||||
|
|
||||||
var opt = {};
|
var opt = {};
|
||||||
|
@ -41,6 +46,12 @@ define([
|
||||||
// 32 more for a signing key
|
// 32 more for a signing key
|
||||||
var edSeed = opt.edSeed = dispense(32);
|
var edSeed = opt.edSeed = dispense(32);
|
||||||
|
|
||||||
|
// 64 more bytes to seed an additional signing key
|
||||||
|
opt.blockSeed = new Uint8Array(dispense(64));
|
||||||
|
|
||||||
|
var blockKeys = opt.blockKeys = Block.genkeys(opt.blockSeed);
|
||||||
|
opt.blockHash = Block.getBlockHash(blockKeys);
|
||||||
|
|
||||||
// derive a private key from the ed seed
|
// derive a private key from the ed seed
|
||||||
var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed));
|
var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed));
|
||||||
|
|
||||||
|
@ -105,18 +116,32 @@ define([
|
||||||
return void cb('PASS_TOO_SHORT');
|
return void cb('PASS_TOO_SHORT');
|
||||||
}
|
}
|
||||||
|
|
||||||
Cred.deriveFromPassphrase(uname, passwd, 128, function (bytes) {
|
// results...
|
||||||
// results...
|
var res = {
|
||||||
var res = {
|
register: isRegister,
|
||||||
register: isRegister,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// run scrypt to derive the user's keys
|
var RT;
|
||||||
var opt = res.opt = allocateBytes(bytes);
|
|
||||||
|
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
Cred.deriveFromPassphrase(uname, passwd, Exports.requiredBytes, waitFor(function (bytes) {
|
||||||
|
// run scrypt to derive the user's keys
|
||||||
|
res.opt = allocateBytes(bytes);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// TODO consider checking the block here
|
||||||
|
}).nThen(function (/* waitFor */) {
|
||||||
|
// check for blocks
|
||||||
|
Block = Block; // jshint
|
||||||
|
|
||||||
|
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
var opt = res.opt;
|
||||||
// use the derived key to generate an object
|
// use the derived key to generate an object
|
||||||
loadUserObject(opt, function (err, rt) {
|
loadUserObject(opt, waitFor(function (err, rt) {
|
||||||
if (err) { return void cb(err); }
|
if (err) { return void cb(err); }
|
||||||
|
RT = rt;
|
||||||
|
|
||||||
res.proxy = rt.proxy;
|
res.proxy = rt.proxy;
|
||||||
res.realtime = rt.realtime;
|
res.realtime = rt.realtime;
|
||||||
|
@ -136,12 +161,14 @@ define([
|
||||||
// they tried to just log in but there's no such user
|
// they tried to just log in but there's no such user
|
||||||
if (!isRegister && isProxyEmpty(rt.proxy)) {
|
if (!isRegister && isProxyEmpty(rt.proxy)) {
|
||||||
rt.network.disconnect(); // clean up after yourself
|
rt.network.disconnect(); // clean up after yourself
|
||||||
|
waitFor.abort();
|
||||||
return void cb('NO_SUCH_USER', res);
|
return void cb('NO_SUCH_USER', res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// they tried to register, but those exact credentials exist
|
// they tried to register, but those exact credentials exist
|
||||||
if (isRegister && !isProxyEmpty(rt.proxy)) {
|
if (isRegister && !isProxyEmpty(rt.proxy)) {
|
||||||
rt.network.disconnect();
|
rt.network.disconnect();
|
||||||
|
waitFor.abort();
|
||||||
return void cb('ALREADY_REGISTERED', res);
|
return void cb('ALREADY_REGISTERED', res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,17 +190,17 @@ define([
|
||||||
if (shouldImport) {
|
if (shouldImport) {
|
||||||
sessionStorage.migrateAnonDrive = 1;
|
sessionStorage.migrateAnonDrive = 1;
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
// We have to call whenRealtimeSyncs asynchronously here because in the current
|
}).nThen(function () {
|
||||||
// version of listmap, onLocal calls `chainpad.contentUpdate(newValue)`
|
// We have to call whenRealtimeSyncs asynchronously here because in the current
|
||||||
// asynchronously.
|
// version of listmap, onLocal calls `chainpad.contentUpdate(newValue)`
|
||||||
// The following setTimeout is here to make sure whenRealtimeSyncs is called after
|
// asynchronously.
|
||||||
// `contentUpdate` so that we have an update userDoc in chainpad.
|
// The following setTimeout is here to make sure whenRealtimeSyncs is called after
|
||||||
setTimeout(function () {
|
// `contentUpdate` so that we have an update userDoc in chainpad.
|
||||||
Realtime.whenRealtimeSyncs(rt.realtime, function () {
|
setTimeout(function () {
|
||||||
LocalStore.login(res.userHash, res.userName, function () {
|
Realtime.whenRealtimeSyncs(RT.realtime, function () {
|
||||||
setTimeout(function () { cb(void 0, res); });
|
LocalStore.login(res.userHash, res.userName, function () {
|
||||||
});
|
setTimeout(function () { cb(void 0, res); });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -600,6 +600,11 @@ define(function () {
|
||||||
out.settings_templateSkip = "Skip the template selection modal";
|
out.settings_templateSkip = "Skip the template selection modal";
|
||||||
out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.";
|
out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.";
|
||||||
|
|
||||||
|
out.settings_ownDriveTitle = "Drive migration"; // XXX
|
||||||
|
out.settings_ownDriveHint = "Migrating your drive to the new version will give you access to new features..."; // XXX
|
||||||
|
out.settings_ownDriveButton = "Migrate"; // XXX
|
||||||
|
out.settings_ownDriveConfirm = "Are you sure?"; // XXX
|
||||||
|
|
||||||
out.settings_changePasswordTitle = "Change your password"; // XXX
|
out.settings_changePasswordTitle = "Change your password"; // XXX
|
||||||
out.settings_changePasswordHint = "Change your account's password without losing its data. You have to enter your existing password once, and the new password you want twice.<br>" +
|
out.settings_changePasswordHint = "Change your account's password without losing its data. You have to enter your existing password once, and the new password you want twice.<br>" +
|
||||||
"<b>We can't reset your password if you forget it so be very careful!</b>"; // XXX
|
"<b>We can't reset your password if you forget it so be very careful!</b>"; // XXX
|
||||||
|
@ -608,6 +613,7 @@ define(function () {
|
||||||
out.settings_changePasswordNew = "New password"; // XXX
|
out.settings_changePasswordNew = "New password"; // XXX
|
||||||
out.settings_changePasswordNewConfirm = "Confirm new password"; // XXX
|
out.settings_changePasswordNewConfirm = "Confirm new password"; // XXX
|
||||||
out.settings_changePasswordConfirm = "Are you sure?"; // XXX
|
out.settings_changePasswordConfirm = "Are you sure?"; // XXX
|
||||||
|
out.settings_changePasswordError = "Error {0}"; // XXX
|
||||||
|
|
||||||
out.upload_title = "File upload";
|
out.upload_title = "File upload";
|
||||||
out.upload_modal_title = "File upload options";
|
out.upload_modal_title = "File upload options";
|
||||||
|
|
173
rpc.js
173
rpc.js
|
@ -1297,6 +1297,160 @@ var upload_status = function (Env, publicKey, filesize, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
We assume that the server is secured against MitM attacks
|
||||||
|
via HTTPS, and that malicious actors do not have code execution
|
||||||
|
capabilities. If they do, we have much more serious problems.
|
||||||
|
|
||||||
|
The capability to replay a block write or remove results in either
|
||||||
|
a denial of service for the user whose block was removed, or in the
|
||||||
|
case of a write, a rollback to an earlier password.
|
||||||
|
|
||||||
|
Since block modification is destructive, this can result in loss
|
||||||
|
of access to the user's drive.
|
||||||
|
|
||||||
|
So long as the detached signature is never observed by a malicious
|
||||||
|
party, and the server discards it after proof of knowledge, replays
|
||||||
|
are not possible. However, this precludes verification of the signature
|
||||||
|
at a later time.
|
||||||
|
|
||||||
|
Despite this, an integrity check is still possible by the original
|
||||||
|
author of the block, since we assume that the block will have been
|
||||||
|
encrypted with xsalsa20-poly1305 which is authenticated.
|
||||||
|
*/
|
||||||
|
var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
|
||||||
|
// convert the public key to a Uint8Array and validate it
|
||||||
|
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
|
||||||
|
|
||||||
|
var u8_public_key;
|
||||||
|
try {
|
||||||
|
u8_public_key = Nacl.util.decodeBase64(publicKey);
|
||||||
|
} catch (e) {
|
||||||
|
return void cb('E_INVALID_KEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
var u8_signature;
|
||||||
|
try {
|
||||||
|
u8_signature = Nacl.util.decodeBase64(signature);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return void cb('E_INVALID_SIGNATURE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the block to a Uint8Array
|
||||||
|
var u8_block;
|
||||||
|
try {
|
||||||
|
u8_block = Nacl.util.decodeBase64(block);
|
||||||
|
} catch (e) {
|
||||||
|
return void cb('E_INVALID_BLOCK');
|
||||||
|
}
|
||||||
|
|
||||||
|
// take its hash
|
||||||
|
var hash = Nacl.hash(u8_block);
|
||||||
|
|
||||||
|
// validate the signature against the hash of the content
|
||||||
|
var verified = Nacl.sign.detached.verify(hash, u8_signature, u8_public_key);
|
||||||
|
|
||||||
|
// existing authentication ensures that users cannot replay old blocks
|
||||||
|
|
||||||
|
// call back with (err) if unsuccessful
|
||||||
|
if (!verified) { return void cb("E_COULD_NOT_VERIFY"); }
|
||||||
|
|
||||||
|
return void cb(null, u8_block);
|
||||||
|
|
||||||
|
// signature 64 bytes
|
||||||
|
// sign.detached(hash(decodeBase64_content(base64_content)), decodeBase64(publicKey))
|
||||||
|
|
||||||
|
// 1 byte version
|
||||||
|
// base64_content
|
||||||
|
};
|
||||||
|
|
||||||
|
var createLoginBlockPath = function (Env, publicKey) {
|
||||||
|
// prepare publicKey to be used as a file name
|
||||||
|
var safeKey = escapeKeyCharacters(publicKey);
|
||||||
|
|
||||||
|
// validate safeKey
|
||||||
|
if (typeof(safeKey) !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive the full path
|
||||||
|
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
|
||||||
|
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
var writeLoginBlock = function (Env, msg, cb) {
|
||||||
|
//console.log(msg);
|
||||||
|
var publicKey = msg[0];
|
||||||
|
var signature = msg[1];
|
||||||
|
var block = msg[2];
|
||||||
|
|
||||||
|
validateLoginBlock(Env, publicKey, signature, block, function (e, verified_block) {
|
||||||
|
if (e) { return void cb(e); }
|
||||||
|
|
||||||
|
// derive the filepath
|
||||||
|
var path = createLoginBlockPath(Env, publicKey);
|
||||||
|
|
||||||
|
// make sure the path is valid
|
||||||
|
if (typeof(path) !== 'string') {
|
||||||
|
return void cb('E_INVALID_BLOCK_PATH');
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed = Path.parse(path);
|
||||||
|
if (!parsed || typeof(parsed.dir) !== 'string') {
|
||||||
|
return void cb("E_INVALID_BLOCK_PATH_2");
|
||||||
|
}
|
||||||
|
|
||||||
|
nThen(function (w) {
|
||||||
|
// make sure the path to the file exists
|
||||||
|
Mkdirp(parsed.dir, w(function (e) {
|
||||||
|
if (e) {
|
||||||
|
w.abort();
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}).nThen(function () {
|
||||||
|
// actually write the block
|
||||||
|
Fs.writeFile(path, new Buffer(verified_block), { encoding: "binary", }, function (err) {
|
||||||
|
if (err) { return void cb(err); }
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
When users write a block, they upload the block, and provide
|
||||||
|
a signature proving that they deserve to be able to write to
|
||||||
|
the location determined by the public key.
|
||||||
|
|
||||||
|
When removing a block, there is nothing to upload, but we need
|
||||||
|
to sign something. Since the signature is considered sensitive
|
||||||
|
information, we can just sign some constant and use that as proof.
|
||||||
|
|
||||||
|
*/
|
||||||
|
var removeLoginBlock = function (Env, msg, cb) {
|
||||||
|
var publicKey = msg[0];
|
||||||
|
var signature = msg[1];
|
||||||
|
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
|
||||||
|
|
||||||
|
validateLoginBlock(Env, publicKey, signature, block, function (e) {
|
||||||
|
if (e) { return void cb(e); }
|
||||||
|
// derive the filepath
|
||||||
|
var path = createLoginBlockPath(Env, publicKey);
|
||||||
|
|
||||||
|
// make sure the path is valid
|
||||||
|
if (typeof(path) !== 'string') {
|
||||||
|
return void cb('E_INVALID_BLOCK_PATH');
|
||||||
|
}
|
||||||
|
|
||||||
|
Fs.unlink(path, function (err) {
|
||||||
|
if (err) { return void cb(err); }
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var isNewChannel = function (Env, channel, cb) {
|
var isNewChannel = function (Env, channel, cb) {
|
||||||
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||||
if (channel.length !== 32) { return void cb('INVALID_CHAN'); }
|
if (channel.length !== 32) { return void cb('INVALID_CHAN'); }
|
||||||
|
@ -1353,6 +1507,8 @@ var isAuthenticatedCall = function (call) {
|
||||||
'CLEAR_OWNED_CHANNEL',
|
'CLEAR_OWNED_CHANNEL',
|
||||||
'REMOVE_OWNED_CHANNEL',
|
'REMOVE_OWNED_CHANNEL',
|
||||||
'REMOVE_PINS',
|
'REMOVE_PINS',
|
||||||
|
'WRITE_LOGIN_BLOCK',
|
||||||
|
'REMOVE_LOGIN_BLOCK',
|
||||||
].indexOf(call) !== -1;
|
].indexOf(call) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1423,6 +1579,7 @@ RPC.create = function (
|
||||||
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
||||||
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||||
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||||
|
paths.block = keyOrDefaultString('blockPath', './block');
|
||||||
|
|
||||||
var isUnauthenticateMessage = function (msg) {
|
var isUnauthenticateMessage = function (msg) {
|
||||||
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
||||||
|
@ -1692,6 +1849,22 @@ RPC.create = function (
|
||||||
WARN(e, 'UPLOAD_CANCEL');
|
WARN(e, 'UPLOAD_CANCEL');
|
||||||
Respond(e);
|
Respond(e);
|
||||||
});
|
});
|
||||||
|
case 'WRITE_LOGIN_BLOCK':
|
||||||
|
return void writeLoginBlock(Env, msg[1], function (e) {
|
||||||
|
if (e) {
|
||||||
|
WARN(e, 'WRITE_LOGIN_BLOCK');
|
||||||
|
return void Respond(e);
|
||||||
|
}
|
||||||
|
Respond(e);
|
||||||
|
});
|
||||||
|
case 'REMOVE_LOGIN_BLOCK':
|
||||||
|
return void removeLoginBlock(Env, msg[1], function (e) {
|
||||||
|
if (e) {
|
||||||
|
WARN(e, 'REMOVE_LOGIN_BLOCK');
|
||||||
|
return void Respond(e);
|
||||||
|
}
|
||||||
|
Respond(e);
|
||||||
|
});
|
||||||
default:
|
default:
|
||||||
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,9 @@ app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob
|
||||||
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
|
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
|
||||||
maxAge: "0d"
|
maxAge: "0d"
|
||||||
}));
|
}));
|
||||||
|
app.use("/block", Express.static(Path.join(__dirname, (config.blockPath || '/block')), {
|
||||||
|
maxAge: "0d",
|
||||||
|
}));
|
||||||
|
|
||||||
app.use("/customize", Express.static(__dirname + '/customize'));
|
app.use("/customize", Express.static(__dirname + '/customize'));
|
||||||
app.use("/customize", Express.static(__dirname + '/customize.dist'));
|
app.use("/customize", Express.static(__dirname + '/customize.dist'));
|
||||||
|
|
|
@ -10,9 +10,13 @@ define([
|
||||||
'/common/wire.js',
|
'/common/wire.js',
|
||||||
'/common/flat-dom.js',
|
'/common/flat-dom.js',
|
||||||
'/common/media-tag.js',
|
'/common/media-tag.js',
|
||||||
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) {
|
'/common/outer/login-block.js',
|
||||||
|
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag, Block) {
|
||||||
window.Hyperjson = Hyperjson;
|
window.Hyperjson = Hyperjson;
|
||||||
window.Sortify = Sortify;
|
window.Sortify = Sortify;
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
|
||||||
var assertions = 0;
|
var assertions = 0;
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
@ -296,6 +300,15 @@ define([
|
||||||
!secret.hashData.present);
|
!secret.hashData.present);
|
||||||
}, "test support for ugly tracking query paramaters in url");
|
}, "test support for ugly tracking query paramaters in url");
|
||||||
|
|
||||||
|
assert(function (cb) {
|
||||||
|
var keys = Block.genkeys(Nacl.randomBytes(64));
|
||||||
|
var hash = Block.getBlockHash(keys);
|
||||||
|
var parsed = Block.parseBlockHash(hash);
|
||||||
|
|
||||||
|
cb(parsed &&
|
||||||
|
parsed.keys.symmetric.length === keys.symmetric.length);
|
||||||
|
}, 'parse a block hash');
|
||||||
|
|
||||||
assert(function (cb) {
|
assert(function (cb) {
|
||||||
try {
|
try {
|
||||||
MediaTag(void 0).on('progress').on('decryption');
|
MediaTag(void 0).on('progress').on('decryption');
|
||||||
|
|
|
@ -3,6 +3,7 @@ define(function () {
|
||||||
// localStorage
|
// localStorage
|
||||||
userHashKey: 'User_hash',
|
userHashKey: 'User_hash',
|
||||||
userNameKey: 'User_name',
|
userNameKey: 'User_name',
|
||||||
|
blockHashKey: 'Block_hash',
|
||||||
fileHashKey: 'FS_hash',
|
fileHashKey: 'FS_hash',
|
||||||
// sessionStorage
|
// sessionStorage
|
||||||
newPadPathKey: "newPadPath",
|
newPadPathKey: "newPadPath",
|
||||||
|
@ -11,6 +12,7 @@ define(function () {
|
||||||
oldStorageKey: 'CryptPad_RECENTPADS',
|
oldStorageKey: 'CryptPad_RECENTPADS',
|
||||||
storageKey: 'filesData',
|
storageKey: 'filesData',
|
||||||
tokenKey: 'loginToken',
|
tokenKey: 'loginToken',
|
||||||
displayPadCreationScreen: 'displayPadCreationScreen'
|
displayPadCreationScreen: 'displayPadCreationScreen',
|
||||||
|
deprecatedKey: 'deprecated'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,6 +83,21 @@ define([], function () {
|
||||||
}).join('');
|
}).join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// given an array of Uint8Arrays, return a new Array with all their values
|
||||||
|
Util.uint8ArrayJoin = function (AA) {
|
||||||
|
var l = 0;
|
||||||
|
var i = 0;
|
||||||
|
for (; i < AA.length; i++) { l += AA[i].length; }
|
||||||
|
var C = new Uint8Array(l);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (var offset = 0; i < AA.length; i++) {
|
||||||
|
C.set(AA[i], offset);
|
||||||
|
offset += AA[i].length;
|
||||||
|
}
|
||||||
|
return C;
|
||||||
|
};
|
||||||
|
|
||||||
Util.deduplicateString = function (array) {
|
Util.deduplicateString = function (array) {
|
||||||
var a = array.slice();
|
var a = array.slice();
|
||||||
for(var i=0; i<a.length; i++) {
|
for(var i=0; i<a.length; i++) {
|
||||||
|
@ -122,17 +137,14 @@ define([], function () {
|
||||||
else if (bytes >= oneMegabyte) { return 'MB'; }
|
else if (bytes >= oneMegabyte) { return 'MB'; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// given a path, asynchronously return an arraybuffer
|
||||||
Util.fetch = function (src, cb) {
|
Util.fetch = function (src, cb) {
|
||||||
var done = false;
|
var CB = Util.once(cb);
|
||||||
var CB = function (err, res) {
|
|
||||||
if (done) { return; }
|
|
||||||
done = true;
|
|
||||||
cb(err, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("GET", src, true);
|
xhr.open("GET", src, true);
|
||||||
xhr.responseType = "arraybuffer";
|
xhr.responseType = "arraybuffer";
|
||||||
|
xhr.onerror = function (err) { CB(err); };
|
||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
if (/^4/.test(''+this.status)) {
|
if (/^4/.test(''+this.status)) {
|
||||||
return CB('XHR_ERROR');
|
return CB('XHR_ERROR');
|
||||||
|
|
|
@ -8,11 +8,12 @@ define([
|
||||||
'/common/common-feedback.js',
|
'/common/common-feedback.js',
|
||||||
'/common/outer/local-store.js',
|
'/common/outer/local-store.js',
|
||||||
'/common/outer/worker-channel.js',
|
'/common/outer/worker-channel.js',
|
||||||
|
'/common/outer/login-block.js',
|
||||||
|
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
'/bower_components/nthen/index.js',
|
'/bower_components/nthen/index.js',
|
||||||
], function (Config, Messages, Util, Hash,
|
], function (Config, Messages, Util, Hash,
|
||||||
Messaging, Constants, Feedback, LocalStore, Channel,
|
Messaging, Constants, Feedback, LocalStore, Channel, Block,
|
||||||
AppConfig, Nthen) {
|
AppConfig, Nthen) {
|
||||||
|
|
||||||
/* This file exposes functionality which is specific to Cryptpad, but not to
|
/* This file exposes functionality which is specific to Cryptpad, but not to
|
||||||
|
@ -240,6 +241,12 @@ define([
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
common.removeLoginBlock = function (data, cb) {
|
||||||
|
postMessage('REMOVE_LOGIN_BLOCK', data, function (obj) {
|
||||||
|
cb(obj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// ANON RPC
|
// ANON RPC
|
||||||
|
|
||||||
// SFRAME: talk to anon_rpc from the iframe
|
// SFRAME: talk to anon_rpc from the iframe
|
||||||
|
@ -692,6 +699,157 @@ define([
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
common.changeUserPassword = function (Crypt, edPublic, data, cb) {
|
||||||
|
if (!edPublic) {
|
||||||
|
return void cb({
|
||||||
|
error: 'E_NOT_LOGGED_IN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var accountName = LocalStore.getAccountName();
|
||||||
|
var hash = LocalStore.getUserHash(); // To load your old drive
|
||||||
|
var password = data.password; // To remove your old block
|
||||||
|
var newPassword = data.newPassword; // To create your new block
|
||||||
|
var secret = Hash.getSecrets('drive', hash);
|
||||||
|
var newHash, newHref, newSecret, newBlockSeed;
|
||||||
|
var oldIsOwned = false;
|
||||||
|
|
||||||
|
var blockHash = LocalStore.getBlockHash();
|
||||||
|
var oldBlockKeys;
|
||||||
|
|
||||||
|
var Cred, Block, Login;
|
||||||
|
Nthen(function (waitFor) {
|
||||||
|
require([
|
||||||
|
'/customize/credential.js',
|
||||||
|
'/common/outer/login-block.js',
|
||||||
|
'/customize/login.js'
|
||||||
|
], waitFor(function (_Cred, _Block, _Login) {
|
||||||
|
Cred = _Cred;
|
||||||
|
Block = _Block;
|
||||||
|
Login = _Login;
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// confirm that the provided password is correct
|
||||||
|
Cred.deriveFromPassphrase(accountName, password, Login.requiredBytes, waitFor(function (bytes) {
|
||||||
|
var allocated = Login.allocateBytes(bytes);
|
||||||
|
oldBlockKeys = allocated.blockKeys;
|
||||||
|
if (blockHash) {
|
||||||
|
if (blockHash !== allocated.blockHash) {
|
||||||
|
// incorrect password probably
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb({
|
||||||
|
error: 'INVALID_PASSWORD',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// the user has already created a block, so you should compare against that
|
||||||
|
} else {
|
||||||
|
// otherwise they're a legacy user, and we should check against the User_hash
|
||||||
|
if (hash !== allocated.userHash) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb({
|
||||||
|
error: 'INVALID_PASSWORD',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Check if our drive is already owned
|
||||||
|
common.anonRpcMsg('GET_METADATA', secret.channel, waitFor(function (err, obj) {
|
||||||
|
if (err || obj.error) { return; }
|
||||||
|
if (obj.owners && Array.isArray(obj.owners) &&
|
||||||
|
obj.owners.indexOf(edPublic) !== -1) {
|
||||||
|
oldIsOwned = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Create a new user hash
|
||||||
|
// Get the current content, store it in the new user file
|
||||||
|
// and make sure the new user drive is owned
|
||||||
|
newHash = Hash.createRandomHash('drive');
|
||||||
|
newHref = '/drive/#' + newHash;
|
||||||
|
newSecret = Hash.getSecrets('drive', newHash);
|
||||||
|
|
||||||
|
var optsPut = {
|
||||||
|
owners: [edPublic]
|
||||||
|
};
|
||||||
|
|
||||||
|
Crypt.get(hash, waitFor(function (err, val) {
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb({ error: err });
|
||||||
|
}
|
||||||
|
Crypt.put(newHash, val, waitFor(function (err) {
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb({ error: err });
|
||||||
|
}
|
||||||
|
}), optsPut);
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Drive content copied: get the new block location
|
||||||
|
Cred.deriveFromPassphrase(accountName, newPassword, Login.requiredBytes, waitFor(function (bytes) {
|
||||||
|
var allocated = Login.allocateBytes(bytes);
|
||||||
|
newBlockSeed = allocated.blockSeed;
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Write the new login block
|
||||||
|
var keys = Block.genkeys(newBlockSeed);
|
||||||
|
var content = Block.serialize(JSON.stringify({
|
||||||
|
User_name: accountName,
|
||||||
|
User_hash: newHash
|
||||||
|
}), keys);
|
||||||
|
common.writeLoginBlock(content, waitFor(function (obj) {
|
||||||
|
var newBlockHash = Block.getBlockHash(keys);
|
||||||
|
LocalStore.setBlockHash(newBlockHash);
|
||||||
|
if (obj && obj.error) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb(obj);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// New drive hash is in login block, unpin the old one and pin the new one
|
||||||
|
common.unpinPads([secret.channel], waitFor());
|
||||||
|
common.pinPads([newSecret.channel], waitFor());
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Remove block hash
|
||||||
|
if (blockHash) {
|
||||||
|
var removeData = Block.remove(oldBlockKeys);
|
||||||
|
common.removeLoginBlock(removeData, waitFor(function (obj) {
|
||||||
|
if (obj && obj.error) { return void console.error(obj.error); }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
if (oldIsOwned) {
|
||||||
|
common.removeOwnedChannel(secret.channel, waitFor(function (obj) {
|
||||||
|
if (obj && obj.error) {
|
||||||
|
// Deal with it as if it was not owned
|
||||||
|
oldIsOwned = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
common.logoutFromAll(waitFor(function () {
|
||||||
|
postMessage("DISCONNECT");
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
if (!oldIsOwned) {
|
||||||
|
postMessage("SET", {
|
||||||
|
key: [Constants.deprecatedKey],
|
||||||
|
value: true
|
||||||
|
}, waitFor(function (obj) {
|
||||||
|
if (obj && obj.error) {
|
||||||
|
console.error(obj.error);
|
||||||
|
}
|
||||||
|
common.logoutFromAll(waitFor(function () {
|
||||||
|
postMessage("DISCONNECT");
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).nThen(function () {
|
||||||
|
// We have the new drive, with the new login block
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Loading events
|
// Loading events
|
||||||
common.loading = {};
|
common.loading = {};
|
||||||
common.loading.onDriveEvent = Util.mkEvent();
|
common.loading.onDriveEvent = Util.mkEvent();
|
||||||
|
@ -887,6 +1045,34 @@ define([
|
||||||
if (AppConfig.beforeLogin) {
|
if (AppConfig.beforeLogin) {
|
||||||
AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor());
|
AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
var blockHash = LocalStore.getBlockHash();
|
||||||
|
if (blockHash) {
|
||||||
|
console.log(blockHash);
|
||||||
|
var parsed = Hash.parseBlockHash(blockHash);
|
||||||
|
|
||||||
|
if (typeof(parsed) !== 'object') {
|
||||||
|
console.error("Failed to parse blockHash");
|
||||||
|
console.log(parsed);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log(parsed);
|
||||||
|
}
|
||||||
|
Util.fetch(parsed.href, waitFor(function (err, arraybuffer) {
|
||||||
|
if (err) { return void console.log(err); }
|
||||||
|
|
||||||
|
// use the results to load your user hash and
|
||||||
|
// put your userhash into localStorage
|
||||||
|
try {
|
||||||
|
var block_info = Block.decrypt(arraybuffer, parsed.keys);
|
||||||
|
if (block_info[Constants.userHashKey]) { LocalStore.setUserHash(block_info[Constants.userHashKey]); }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return void console.error("failed to decrypt or decode block content");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
var cfg = {
|
var cfg = {
|
||||||
init: true,
|
init: true,
|
||||||
|
|
|
@ -13,7 +13,7 @@ define([
|
||||||
'/common/outer/network-config.js',
|
'/common/outer/network-config.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
'/bower_components/chainpad/chainpad.dist.js',
|
'/bower_components/chainpad/chainpad.dist.js',
|
||||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||||
'/bower_components/nthen/index.js',
|
'/bower_components/nthen/index.js',
|
||||||
|
@ -285,6 +285,15 @@ define([
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Store.removeLoginBlock = function (clientId, data, cb) {
|
||||||
|
store.rpc.removeLoginBlock(data, function (e, res) {
|
||||||
|
cb({
|
||||||
|
error: e,
|
||||||
|
data: res
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
Store.initRpc = function (clientId, data, cb) {
|
Store.initRpc = function (clientId, data, cb) {
|
||||||
if (store.rpc) { return void cb(account); }
|
if (store.rpc) { return void cb(account); }
|
||||||
require(['/common/pinpad.js'], function (Pinpad) {
|
require(['/common/pinpad.js'], function (Pinpad) {
|
||||||
|
|
|
@ -58,6 +58,14 @@ define([
|
||||||
localStorage[Constants.userHashKey] = sHash;
|
localStorage[Constants.userHashKey] = sHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LocalStore.getBlockHash = function () {
|
||||||
|
return localStorage[Constants.blockHashKey];
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalStore.setBlockHash = function (hash) {
|
||||||
|
localStorage[Constants.blockHashKey] = hash;
|
||||||
|
};
|
||||||
|
|
||||||
LocalStore.getAccountName = function () {
|
LocalStore.getAccountName = function () {
|
||||||
return localStorage[Constants.userNameKey];
|
return localStorage[Constants.userNameKey];
|
||||||
};
|
};
|
||||||
|
@ -69,7 +77,7 @@ define([
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// XXX update this to take into account blockHash values
|
||||||
LocalStore.login = function (hash, name, cb) {
|
LocalStore.login = function (hash, name, cb) {
|
||||||
if (!hash) { throw new Error('expected a user hash'); }
|
if (!hash) { throw new Error('expected a user hash'); }
|
||||||
if (!name) { throw new Error('expected a user name'); }
|
if (!name) { throw new Error('expected a user name'); }
|
||||||
|
@ -96,6 +104,7 @@ define([
|
||||||
[
|
[
|
||||||
Constants.userNameKey,
|
Constants.userNameKey,
|
||||||
Constants.userHashKey,
|
Constants.userHashKey,
|
||||||
|
Constants.blockHashKey,
|
||||||
'loginToken',
|
'loginToken',
|
||||||
'plan',
|
'plan',
|
||||||
].forEach(function (k) {
|
].forEach(function (k) {
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
define([
|
||||||
|
'/common/common-util.js',
|
||||||
|
'/api/config',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
], function (Util, ApiConfig) {
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
|
||||||
|
var Block = {};
|
||||||
|
|
||||||
|
Block.join = Util.uint8ArrayJoin;
|
||||||
|
|
||||||
|
// publickey <base64 string>
|
||||||
|
|
||||||
|
// signature <base64 string>
|
||||||
|
|
||||||
|
// block <base64 string>
|
||||||
|
|
||||||
|
// [b64_public, b64_sig, b64_block [version, nonce, content]]
|
||||||
|
|
||||||
|
Block.seed = function () {
|
||||||
|
return Nacl.hash(Nacl.util.decodeUTF8('pewpewpew'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// should be deterministic from a seed...
|
||||||
|
Block.genkeys = function (seed) {
|
||||||
|
if (!(seed instanceof Uint8Array)) {
|
||||||
|
throw new Error('INVALID_SEED_FORMAT');
|
||||||
|
}
|
||||||
|
if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
|
||||||
|
throw new Error('INVALID_SEED_LENGTH');
|
||||||
|
}
|
||||||
|
|
||||||
|
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
|
||||||
|
var symmetric = seed.subarray(Nacl.sign.seedLength,
|
||||||
|
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
|
||||||
|
|
||||||
|
console.log("symmetric key: ", Nacl.util.encodeBase64(symmetric));
|
||||||
|
|
||||||
|
return {
|
||||||
|
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
|
||||||
|
symmetric: symmetric, // 32 bytes ...
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// (UTF8 content, keys object) => Uint8Array block
|
||||||
|
Block.encrypt = function (version, content, keys) {
|
||||||
|
var u8 = Nacl.util.decodeUTF8(content);
|
||||||
|
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
|
||||||
|
return Block.join([
|
||||||
|
[0],
|
||||||
|
nonce,
|
||||||
|
Nacl.secretbox(u8, nonce, keys.symmetric)
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// (uint8Array block) => payload object
|
||||||
|
Block.decrypt = function (u8_content, keys) {
|
||||||
|
// version is currently ignored since there is only one
|
||||||
|
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
|
||||||
|
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
|
||||||
|
|
||||||
|
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
|
||||||
|
try {
|
||||||
|
return JSON.parse(Nacl.util.encodeUTF8(plaintext));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// (Uint8Array block) => signature
|
||||||
|
Block.sign = function (ciphertext, keys) {
|
||||||
|
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.serialize = function (content, keys) {
|
||||||
|
// encrypt the content
|
||||||
|
var ciphertext = Block.encrypt(0, content, keys);
|
||||||
|
|
||||||
|
// generate a detached signature
|
||||||
|
var sig = Block.sign(ciphertext, keys);
|
||||||
|
|
||||||
|
// serialize {publickey, sig, ciphertext}
|
||||||
|
return {
|
||||||
|
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
|
||||||
|
signature: Nacl.util.encodeBase64(sig),
|
||||||
|
ciphertext: Nacl.util.encodeBase64(ciphertext),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.remove = function (keys) {
|
||||||
|
// sign the hash of the text 'DELETE_BLOCK'
|
||||||
|
var sig = Nacl.sign.detached(Nacl.hash(
|
||||||
|
Nacl.util.decodeUTF8('DELETE_BLOCK')), keys.sign.secretKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
|
||||||
|
signature: Nacl.util.encodeBase64(sig),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME don't spread the functions below across this file and common-hash
|
||||||
|
// find a permanent home for these hacks
|
||||||
|
var urlSafeB64 = function (u8) {
|
||||||
|
return Nacl.util.encodeBase64(u8).replace(/\//g, '-');
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.getBlockHash = function (keys) {
|
||||||
|
var publicKey = urlSafeB64(keys.sign.publicKey);
|
||||||
|
// 'block/' here is hardcoded because it's hardcoded on the server
|
||||||
|
// if we want to make CryptPad work in server subfolders, we'll need
|
||||||
|
// to update this path derivation
|
||||||
|
var relative = 'block/' + publicKey.slice(0, 2) + '/' + publicKey;
|
||||||
|
var symmetric = urlSafeB64(keys.symmetric);
|
||||||
|
return ApiConfig.httpUnsafeOrigin + relative + '#' + symmetric;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Block.createBlockHash = function (href, key) {
|
||||||
|
if (typeof(href) !== 'string') { return; }
|
||||||
|
if (!(key instanceof Uint8Array)) { return; }
|
||||||
|
|
||||||
|
try { return href + '#' + Nacl.util.encodeBase64(key); }
|
||||||
|
catch (e) { return; }
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
var decodeSafeB64 = function (b64) {
|
||||||
|
try {
|
||||||
|
return Nacl.util.decodeBase64(b64.replace(/\-/g, '/'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.parseBlockHash = function (hash) {
|
||||||
|
if (typeof(hash) !== 'string') { return; }
|
||||||
|
var parts = hash.split('#');
|
||||||
|
if (parts.length !== 2) { return; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
href: parts[0],
|
||||||
|
keys: {
|
||||||
|
symmetric: decodeSafeB64(parts[1]),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Block;
|
||||||
|
});
|
|
@ -24,6 +24,7 @@ define([
|
||||||
UPLOAD_STATUS: Store.uploadStatus,
|
UPLOAD_STATUS: Store.uploadStatus,
|
||||||
UPLOAD_CANCEL: Store.uploadCancel,
|
UPLOAD_CANCEL: Store.uploadCancel,
|
||||||
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
|
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
|
||||||
|
REMOVE_LOGIN_BLOCK: Store.removeLoginBlock,
|
||||||
PIN_PADS: Store.pinPads,
|
PIN_PADS: Store.pinPads,
|
||||||
UNPIN_PADS: Store.unpinPads,
|
UNPIN_PADS: Store.unpinPads,
|
||||||
GET_DELETED_PADS: Store.getDeletedPads,
|
GET_DELETED_PADS: Store.getDeletedPads,
|
||||||
|
|
|
@ -222,7 +222,34 @@ define([
|
||||||
};
|
};
|
||||||
|
|
||||||
exp.writeLoginBlock = function (data, cb) {
|
exp.writeLoginBlock = function (data, cb) {
|
||||||
cb();
|
if (!data) { return void cb('NO_DATA'); }
|
||||||
|
if (!data.publicKey || !data.signature || !data.ciphertext) {
|
||||||
|
console.log(data);
|
||||||
|
return void cb("MISSING_PARAMETERS");
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.send('WRITE_LOGIN_BLOCK', [
|
||||||
|
data.publicKey,
|
||||||
|
data.signature,
|
||||||
|
data.ciphertext
|
||||||
|
], function (e) {
|
||||||
|
cb(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.removeLoginBlock = function (data, cb) {
|
||||||
|
if (!data) { return void cb('NO_DATA'); }
|
||||||
|
if (!data.publicKey || !data.signature) {
|
||||||
|
console.log(data);
|
||||||
|
return void cb("MISSING_PARAMETERS");
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.send('REMOVE_LOGIN_BLOCK', [
|
||||||
|
data.publicKey, // publicKey
|
||||||
|
data.signature, // signature
|
||||||
|
], function (e) {
|
||||||
|
cb(e);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
cb(e, exp);
|
cb(e, exp);
|
||||||
|
|
|
@ -661,10 +661,18 @@ define([
|
||||||
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
|
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
|
||||||
|
Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
|
||||||
|
});
|
||||||
|
|
||||||
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
|
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
|
||||||
Cryptpad.writeLoginBlock(data, cb);
|
Cryptpad.writeLoginBlock(data, cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sframeChan.on('Q_REMOVE_LOGIN_BLOCK', function (data, cb) {
|
||||||
|
Cryptpad.removeLoginBlock(data, cb);
|
||||||
|
});
|
||||||
|
|
||||||
if (cfg.addRpc) {
|
if (cfg.addRpc) {
|
||||||
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,9 @@ define({
|
||||||
// Write/update the login block when the account password is changed
|
// Write/update the login block when the account password is changed
|
||||||
'Q_WRITE_LOGIN_BLOCK': true,
|
'Q_WRITE_LOGIN_BLOCK': true,
|
||||||
|
|
||||||
|
// Remove login blocks
|
||||||
|
'Q_REMOVE_LOGIN_BLOCK': true,
|
||||||
|
|
||||||
// Check the pin limit to determine if we can store the pad in the drive or if we should.
|
// Check the pin limit to determine if we can store the pad in the drive or if we should.
|
||||||
// display a warning
|
// display a warning
|
||||||
'Q_GET_PIN_LIMIT_STATUS': true,
|
'Q_GET_PIN_LIMIT_STATUS': true,
|
||||||
|
@ -235,6 +238,9 @@ define({
|
||||||
// Change pad password
|
// Change pad password
|
||||||
'Q_PAD_PASSWORD_CHANGE': true,
|
'Q_PAD_PASSWORD_CHANGE': true,
|
||||||
|
|
||||||
|
// Migrate drive to owned drive
|
||||||
|
'Q_CHANGE_USER_PASSWORD': true,
|
||||||
|
|
||||||
// Loading events to display in the loading screen
|
// Loading events to display in the loading screen
|
||||||
'EV_LOADING_INFO': true,
|
'EV_LOADING_INFO': true,
|
||||||
// Critical error outside the iframe during loading screen
|
// Critical error outside the iframe during loading screen
|
||||||
|
|
|
@ -50,7 +50,7 @@ define([
|
||||||
'cp-settings-resettips',
|
'cp-settings-resettips',
|
||||||
'cp-settings-thumbnails',
|
'cp-settings-thumbnails',
|
||||||
'cp-settings-userfeedback',
|
'cp-settings-userfeedback',
|
||||||
//'cp-settings-change-password',
|
'cp-settings-change-password',
|
||||||
'cp-settings-delete'
|
'cp-settings-delete'
|
||||||
],
|
],
|
||||||
'creation': [
|
'creation': [
|
||||||
|
@ -404,12 +404,11 @@ define([
|
||||||
$(form).appendTo($div);
|
$(form).appendTo($div);
|
||||||
|
|
||||||
var updateBlock = function (data, cb) {
|
var updateBlock = function (data, cb) {
|
||||||
sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) {
|
sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
|
||||||
if (err || obj.error) { return void cb ({error: err || obj.error}); }
|
if (err || obj.error) { return void cb ({error: err || obj.error}); }
|
||||||
cb (obj);
|
cb (obj);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
updateBlock = updateBlock; // jshint..
|
|
||||||
|
|
||||||
var todo = function () {
|
var todo = function () {
|
||||||
var oldPassword = $(form).find('#cp-settings-change-password-current').val();
|
var oldPassword = $(form).find('#cp-settings-change-password-current').val();
|
||||||
|
@ -432,8 +431,15 @@ define([
|
||||||
UI.confirm(Messages.settings_changePasswordConfirm,
|
UI.confirm(Messages.settings_changePasswordConfirm,
|
||||||
function (yes) {
|
function (yes) {
|
||||||
if (!yes) { return; }
|
if (!yes) { return; }
|
||||||
// TODO
|
updateBlock({
|
||||||
console.log(oldPassword, newPassword, newPasswordConfirm);
|
password: oldPassword,
|
||||||
|
newPassword: newPassword
|
||||||
|
}, function (obj) {
|
||||||
|
if (obj && obj.error) {
|
||||||
|
// TODO
|
||||||
|
UI.alert(Messages.settings_changePasswordError);
|
||||||
|
}
|
||||||
|
});
|
||||||
}, {
|
}, {
|
||||||
ok: Messages.register_writtenPassword,
|
ok: Messages.register_writtenPassword,
|
||||||
cancel: Messages.register_cancel,
|
cancel: Messages.register_cancel,
|
||||||
|
@ -461,6 +467,50 @@ define([
|
||||||
return $div;
|
return $div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
create['migrate'] = function () {
|
||||||
|
if (true) { return; } // XXX js hint
|
||||||
|
// TODO
|
||||||
|
// if (!loginBlock) { return; }
|
||||||
|
// if (alreadyMigrated) { return; }
|
||||||
|
if (!common.isLoggedIn()) { return; }
|
||||||
|
|
||||||
|
var $div = $('<div>', { 'class': 'cp-settings-migrate cp-sidebarlayout-element'});
|
||||||
|
|
||||||
|
$('<span>', {'class': 'label'}).text(Messages.settings_ownDriveTitle).appendTo($div);
|
||||||
|
|
||||||
|
$('<span>', {'class': 'cp-sidebarlayout-description'})
|
||||||
|
.append(Messages.settings_ownDriveHint).appendTo($div);
|
||||||
|
|
||||||
|
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
|
||||||
|
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
|
||||||
|
|
||||||
|
var $button = $('<button>', {'id': 'cp-settings-delete', 'class': 'btn btn-primary'})
|
||||||
|
.text(Messages.settings_ownDriveButton).appendTo($div);
|
||||||
|
|
||||||
|
$button.click(function () {
|
||||||
|
$spinner.show();
|
||||||
|
UI.confirm(Messages.settings_ownDriveConfirm, function (yes) {
|
||||||
|
if (!yes) { return; }
|
||||||
|
sframeChan.query("Q_OWN_USER_DRIVE", null, function (err, data) {
|
||||||
|
if (err || data.error) {
|
||||||
|
console.error(err || data.error);
|
||||||
|
// TODO
|
||||||
|
$spinner.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: drive is migrated, autoamtic redirect from outer?
|
||||||
|
$ok.show();
|
||||||
|
$spinner.hide();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$spinner.hide().appendTo($div);
|
||||||
|
$ok.hide().appendTo($div);
|
||||||
|
|
||||||
|
return $div;
|
||||||
|
};
|
||||||
|
|
||||||
// Pad Creation settings
|
// Pad Creation settings
|
||||||
|
|
||||||
var setHTML = function (e, html) {
|
var setHTML = function (e, html) {
|
||||||
|
|
Loading…
Reference in New Issue