From a718603b367d02833aac90d67d93b4d80ad370d0 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 21 Mar 2018 18:27:20 +0100 Subject: [PATCH] Automatic account deletion --- bower.json | 3 +- customize.dist/translations/messages.el.js | 2 +- customize.dist/translations/messages.es.js | 2 +- customize.dist/translations/messages.fr.js | 4 +- customize.dist/translations/messages.js | 4 +- customize.dist/translations/messages.pt-br.js | 2 +- customize.dist/translations/messages.ro.js | 2 +- customize.dist/translations/messages.zh.js | 2 +- rpc.js | 2 +- www/common/common-ui-elements.js | 2 +- www/common/cryptpad-common.js | 14 +++- www/common/outer/async-store.js | 78 ++++++++++++++++--- www/common/outer/local-store.js | 10 ++- www/common/pinpad.js | 11 +++ www/common/sframe-app-framework.js | 2 +- www/common/sframe-chainpad-netflux-inner.js | 2 +- www/common/sframe-common-outer.js | 1 + www/common/sframe-common.js | 30 +++---- www/settings/inner.js | 30 +++++-- 19 files changed, 153 insertions(+), 50 deletions(-) diff --git a/bower.json b/bower.json index f9ee60c20..e2d61a268 100644 --- a/bower.json +++ b/bower.json @@ -46,7 +46,8 @@ "localforage": "^1.5.2", "html2canvas": "^0.4.1", "croppie": "^2.5.0", - "sortablejs": "#^1.6.0" + "sortablejs": "#^1.6.0", + "saferphore": "^0.0.1" }, "resolutions": { "bootstrap": "^v4.0.0" diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js index 36a19da87..493be541d 100644 --- a/customize.dist/translations/messages.el.js +++ b/customize.dist/translations/messages.el.js @@ -31,7 +31,7 @@ define(function () { out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...'; out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή"; - out.onLogout = 'Έχετε αποσυνδεθεί, κάντε "κλικ" εδώ για να συνδεθείτε
ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; + out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε
ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας."; out.loading = "Φόρτωση..."; diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 9e246294f..fc905870a 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -152,7 +152,7 @@ define(function () { out.websocketError = "Error al conectarse al servidor WebSocket"; out.typeError = "Este documento no es compatible con la aplicación seleccionada"; - out.onLogout = "Tu sesión está cerrada, haz clic aquí para iniciar sesión
o pulsa Escape para acceder al documento en modo sólo lectura."; + out.onLogout = "Tu sesión está cerrada, {0}haz clic aquí{1} para iniciar sesión
o pulsa Escape para acceder al documento en modo sólo lectura."; out.loading = "Cargando..."; out.error = "Error"; out.language = "Idioma"; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 37e1cfb18..47bce2af8 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -27,7 +27,7 @@ define(function () { out.websocketError = 'Impossible de se connecter au serveur WebSocket...'; out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée"; - out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, cliquez ici pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; + out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; @@ -546,6 +546,8 @@ define(function () { out.settings_deleteHint = "La suppression de votre compte utilisateur est permanente. Votre CryptDrive et votre liste de pads seront supprimés du serveur. Le reste de vos pads sera supprimé après 90 jours d'inactivité si personne ne les a stockés dans leur CryptDrive."; out.settings_deleteButton = "Supprimer votre compte"; out.settings_deleteModal = "Veuillez envoyer les informations suivantes à votre administrateur CryptPad afin que vos données soient supprimées du serveur."; + out.settings_deleteConfirm = "Êtes-vous sûr de vouloir supprimer votre compte utilisateur ? Cette action est irréversible."; + out.settings_deleted = "Votre compte utilisateur a été supprimé. Appuyez sur OK pour être rédirigé(e) vers la page d'accueil."; out.settings_anonymous = "Vous n'êtes pas connecté. Ces préférences seront utilisées pour ce navigateur."; out.settings_publicSigningKey = "Clé publique de signature"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 868d107cd..6ab51d35d 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -28,7 +28,7 @@ define(function () { out.websocketError = 'Unable to connect to the websocket server...'; out.typeError = "This pad is not compatible with the selected application"; - out.onLogout = 'You are logged out, click here to log in
or press Escape to access your pad in read-only mode.'; + out.onLogout = 'You are logged out, {0}click here{1} to log in
or press Escape to access your pad in read-only mode.'; out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.'; out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; @@ -550,6 +550,8 @@ define(function () { out.settings_deleteHint = "Account deletion is permanent. Your CryptDrive and your list of pads will be deleted from the server. The rest of your pads will be deleted in 90 days if nobody else has stored them in their CryptDrive."; out.settings_deleteButton = "Delete your account"; out.settings_deleteModal = "Share the following information with your CryptPad administrator in order to have your data removed from their server."; + out.settings_deleteConfirm = "Clicking OK will delete your account permanently. Are you sure?"; + out.settings_deleted = "Your user account is now deleted. Press OK to go to the home page."; out.settings_anonymous = "You are not logged in. Settings here are specific to this browser."; out.settings_publicSigningKey = "Public Signing Key"; diff --git a/customize.dist/translations/messages.pt-br.js b/customize.dist/translations/messages.pt-br.js index fbf63d067..a2feea5f9 100644 --- a/customize.dist/translations/messages.pt-br.js +++ b/customize.dist/translations/messages.pt-br.js @@ -38,7 +38,7 @@ define(function () { out.websocketError = 'Incapaz de se conectar com o servidor websocket...'; out.typeError = "Este bloco não é compatível com a aplicação selecionada"; - out.onLogout = 'você foi desconectado, clique aqui para se conectar,
ou pressione ESC para acessar seu bloco em modo somente leitura.'; + out.onLogout = 'você foi desconectado, {0}clique aqui{1} para se conectar,
ou pressione ESC para acessar seu bloco em modo somente leitura.'; out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página."; out.loading = "Carregando..."; diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 56ec8b05d..fe24c7057 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -13,7 +13,7 @@ define(function () { out.common_connectionLost = out.updated_0_common_connectionLost; out.websocketError = "Conexiune inexistentă către serverul websocket..."; out.typeError = "Această filă nu este compatibilă cu aplicația aleasă"; - out.onLogout = "Nu mai ești autentificat, apasă aici să te autentifici
sau apasă Escapesă accesezi fila în modul citire."; + out.onLogout = "Nu mai ești autentificat, {0}apasă aici{1} să te autentifici
sau apasă Escapesă accesezi fila în modul citire."; out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina."; out.loading = "Încarcă..."; out.error = "Eroare"; diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js index 25b7fe8c8..422f00f2c 100644 --- a/customize.dist/translations/messages.zh.js +++ b/customize.dist/translations/messages.zh.js @@ -31,7 +31,7 @@ define(function () { out.websocketError = '無法連結上 websocket 伺服器...'; out.typeError = "這個編輯檔與所選的應用程式並不相容"; - out.onLogout = '你已登出, 點擊這裏 來登入
或按Escape 來以唯讀模型使用你的編輯檔案'; + out.onLogout = '你已登出, {0}點擊這裏{1} 來登入
或按Escape 來以唯讀模型使用你的編輯檔案'; out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。"; out.loading = "載入中..."; diff --git a/rpc.js b/rpc.js index ce2bf3210..d6f661155 100644 --- a/rpc.js +++ b/rpc.js @@ -1481,7 +1481,7 @@ RPC.create = function ( case 'REMOVE_PINS': return void removePins(Env, safeKey, function (e, response) { if (e) { return void Respond(e); } - Respond(void 0, response); + Respond(void 0, "OK"); }); // restricted to privileged users... case 'UPLOAD': diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 561d7af6a..7f49078e0 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1148,7 +1148,7 @@ define([ // so we can just use those and only check for errors var $container = $('', {'class':'cp-limit-container'}); var todo = function (err, data) { - if (err) { return void console.error(err); } + if (err || !data) { return void console.error(err || 'No data'); } var usage = data.usage; var limit = data.limit; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e2968aa94..0a9a27f0d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -622,6 +622,12 @@ define([ window.location.href = '/login/'; }; + common.startAccountDeletion = function (cb) { + // Logout other tabs + LocalStore.logout(null, true); + cb(); + }; + var onMessage = function (cmd, data, cb) { cb = cb || function () {}; switch (cmd) { @@ -701,6 +707,10 @@ define([ case 'DRIVE_REMOVE': { common.drive.onRemove.fire(data); break; } + // Account deletion + case 'DELETE_ACCOUNT': { + common.startAccountDeletion(cb); break; + } } }; @@ -779,11 +789,11 @@ define([ if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - if (cfg.userHash && sessionStorage) { + /*if (cfg.userHash && sessionStorage) { // copy User_hash into sessionStorage because cross-domain iframes // on safari replaces localStorage with sessionStorage or something sessionStorage.setItem(Constants.userHashKey, cfg.userHash); - } + }*/ if (cfg.userHash) { var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index b7f47a1d1..a240cf13c 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -16,9 +16,11 @@ define([ '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/nthen/index.js', + '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, CpNfWorker, NetConfig, AppConfig, - Crypto, ChainPad, Listmap) { + Crypto, ChainPad, Listmap, nThen, Saferphore) { var Store = {}; var postMessage = function () {}; @@ -421,28 +423,78 @@ define([ }); }; + var getOwnedPads = function () { + var list = []; + store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { + var data = store.userObject.getFileData(id); + var edPublic = store.proxy.edPublic; + + // Push channels owned by someone else or channel that should have expired + // because of the expiration time + if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { + list.push(Hash.hrefToHexChannelId(data.href)); + } + }); + return list; + }; + var removeOwnedPads = function (waitFor) { + // Delete owned pads + var ownedPads = getOwnedPads(); + var sem = Saferphore.create(10); + ownedPads.forEach(function (c) { + var w = waitFor(); + sem.take(function (give) { + Store.removeOwnedChannel(c, give(function (obj) { + if (obj && obj.error) { console.error(obj.error); } + w(); + })); + }); + }); + }; + Store.deleteAccount = function (data, cb) { - var toSign = { - intent: 'Please delete my account.' - }; + var edPublic = store.proxy.edPublic; var secret = Hash.getSecrets('drive', storeHash); - toSign.drive = secret.channel; - toSign.edPublic = store.proxy.edPublic; - var signKey = Crypto.Nacl.util.decodeBase64(secret.keys.signKey); + console.log(edPublic); + console.log(secret.channel); Store.anonRpcMsg({ msg: 'GET_METADATA', data: secret.channel }, function (data) { - console.log(data[0]); var metadata = data[0]; // Owned drive if (metadata && metadata.owners && metadata.owners.length === 1 && metadata.owners.indexOf(edPublic) !== -1) { - + nThen(function (waitFor) { + var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + store.proxy[Constants.tokenKey] = token; + postMessage("DELETE_ACCOUNT", token, waitFor()); + }).nThen(function (waitFor) { + removeOwnedPads(waitFor); + }).nThen(function (waitFor) { + // Delete Pin Store + store.rpc.removePins(waitFor(function (err) { + if (err) { console.error(err); } + })); + }).nThen(function (waitFor) { + // Delete Drive + Store.removeOwnedChannel(secret.channel, waitFor()); + }).nThen(function () { + store.network.disconnect(); + cb({ + state: true + }); + }); return; } // Not owned drive + var toSign = { + intent: 'Please delete my account.' + }; + toSign.drive = secret.channel; + toSign.edPublic = edPublic; + var signKey = Crypto.Nacl.util.decodeBase64(secret.keys.signKey); var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey); var proofTxt = Crypto.Nacl.util.encodeBase64(proof); cb({ @@ -529,8 +581,12 @@ define([ // Reset the drive part of the userObject (from settings) Store.resetDrive = function (data, cb) { - store.proxy.drive = store.fo.getStructure(); - onSync(cb); + nThen(function (waitFor) { + removeOwnedPads(waitFor); + }).nThen(function (waitFor) { + store.proxy.drive = store.fo.getStructure(); + onSync(cb); + }); }; /** diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 09cc68549..7e93d34e7 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -92,7 +92,7 @@ define([ }); }; var logoutHandlers = []; - LocalStore.logout = function (cb) { + LocalStore.logout = function (cb, isDeletion) { [ Constants.userNameKey, Constants.userHashKey, @@ -112,9 +112,11 @@ define([ } eraseTempSessionValues(); - logoutHandlers.forEach(function (h) { - if (typeof (h) === "function") { h(); } - }); + if (!isDeletion) { + logoutHandlers.forEach(function (h) { + if (typeof (h) === "function") { h(); } + }); + } if (typeof(AppConfig.customizeLogout) === 'function') { return void AppConfig.customizeLogout(cb); diff --git a/www/common/pinpad.js b/www/common/pinpad.js index f38d7fc57..b200f5493 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -165,6 +165,17 @@ define([ }); }; + exp.removePins = function (cb) { + rpc.send('REMOVE_PINS', undefined, function (e, response) { + if (e) { return void cb(e); } + if (response && response.length && response[0] === "OK") { + cb(); + } else { + cb('INVALID_RESPONSE'); + } + }); + }; + exp.uploadComplete = function (cb) { rpc.send('UPLOAD_COMPLETE', null, function (e, res) { if (e) { return void cb(e); } diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index cbf31bb6e..b3fabee61 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -17,7 +17,7 @@ define([ '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', - 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', + 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/customize/src/less2/main.less', ], function ( $, diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 5b8f883c5..d2b54faf2 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -41,7 +41,7 @@ define([ var patchTransformer = config.patchTransformer; var validateContent = config.validateContent; var avgSyncMilliseconds = config.avgSyncMilliseconds; - var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 2; + var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 1; var readOnly = config.readOnly || false; var sframeChan = config.sframeChan; var metadataMgr = config.metadataMgr; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index df933f1a5..0ba403750 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -622,6 +622,7 @@ define([ window.location.hash = hash; }; + console.log(secret.channel); var cpNfCfg = { sframeChan: sframeChan, channel: secret.channel, diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 7433fa2f0..0d513658b 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -397,19 +397,6 @@ define([ UI.addTooltips(); - ctx.sframeChan.on('EV_LOGOUT', function () { - $(window).on('keyup', function (e) { - if (e.keyCode === 27) { - UI.removeLoadingScreen(); - } - }); - UI.addLoadingScreen({hideTips: true}); - UI.errorLoadingScreen(Messages.onLogout, true); - logoutHandlers.forEach(function (h) { - if (typeof (h) === "function") { h(); } - }); - }); - ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) { UI.confirm(confirmMsg, cb, null, true); }); @@ -425,6 +412,23 @@ define([ var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; Feedback.init(feedback); } catch (e) { Feedback.init(false); } + + ctx.sframeChan.on('EV_LOGOUT', function () { + $(window).on('keyup', function (e) { + if (e.keyCode === 27) { + UI.removeLoadingScreen(); + } + }); + UI.addLoadingScreen({hideTips: true}); + var origin = ctx.metadataMgr.getPrivateData().origin; + var href = origin + "/login/"; + var onLogoutMsg = Messages._getKey('onLogout', ['', '']); + UI.errorLoadingScreen(onLogoutMsg, true); + logoutHandlers.forEach(function (h) { + if (typeof (h) === "function") { h(); } + }); + }); + ctx.sframeChan.ready(); cb(funcs); }); diff --git a/www/settings/inner.js b/www/settings/inner.js index 2aa785981..c9e1cf72c 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -326,7 +326,7 @@ define([ $('', {'class': 'cp-sidebarlayout-description'}) .append(Messages.settings_deleteHint).appendTo($div); - //var $ok = $('', {'class': 'fa fa-check', title: Messages.saved}); + var $ok = $('', {'class': 'fa fa-check', title: Messages.saved}); var $spinner = $('', {'class': 'fa fa-spinner fa-pulse'}); var $button = $('