From e1edac8cb1cc8ddbe7906b309fd6d0cd77076fa0 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 26 Jun 2026 16:56:08 +0200 Subject: [PATCH] feat(storage): linked documents - archive linked documents --- lib/commands/admin-rpc.js | 52 +++++++++++++++++++++-------------- lib/commands/channel.js | 10 ++++++- lib/commands/linked.js | 25 ++++++++++++++++- lib/commands/metadata.js | 3 +- www/common/cryptpad-common.js | 3 +- 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 97c1a4bf9..0ff4abb78 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -13,6 +13,7 @@ const Channel = require("./channel"); const Invitation = require("./invitation"); const Users = require("./users"); const Moderators = require("./moderators"); +const Linked = require("./linked"); const BlockStore = require("../storage/block"); const MFA = require("../storage/mfa"); const ArchiveAccount = require('../archive-account'); @@ -195,16 +196,21 @@ var archiveDocument = function (Env, Server, _cb, data) { switch (id.length) { case 32: - return void Env.msgStore.archiveChannel(id, archiveReason, Util.both(cb, function (err) { - Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", { - channelId: id, - reason: reason, - status: err? String(err): "SUCCESS", - }); - Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reasonStr, err => { - if (err) { } // TODO - }); - })); + return void Linked.listLinkedDocuments(Env, id, (err, channels) => { + Env.msgStore.archiveChannel(id, archiveReason, Util.both(cb, function (err) { + if (!err && channels) { + Linked.archiveLinkedData(Env, id, archiveReason, channels, () => {}); + } + Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", { + channelId: id, + reason: reason, + status: err? String(err): "SUCCESS", + }); + Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reasonStr, err => { + if (err) { } // TODO + }); + })); + }); case 48: return void Env.blobStore.archive.blob(id, archiveReason, Util.both(cb, function (err) { Env.Log.info("ARCHIVAL_BLOB_BY_ADMIN_RPC", { @@ -260,16 +266,22 @@ var removeDocument = function (Env, Server, cb, data) { switch (id.length) { case 32: - return void Env.msgStore.removeChannel(id, Util.both(cb, function (err) { - Env.Log.info("REMOVAL_CHANNEL_BY_ADMIN_RPC", { - channelId: id, - reason: reason, - status: err? String(err): "SUCCESS", - }); - Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reason, err => { - if (err) { } // TODO - }); - })); + return void Linked.listLinkedDocuments(Env, id, (err, channels) => { + Env.msgStore.removeChannel(id, Util.both(cb, function (err) { + if (!err && channels) { + Linked.archiveLinkedData(Env, id, reason, channels, () => {}); + } + Env.Log.info("REMOVAL_CHANNEL_BY_ADMIN_RPC", { + channelId: id, + reason: reason, + status: err? String(err): "SUCCESS", + }); + if (!err) { Linked.archiveLinkedData(Env, id, reason, () => {}); } + Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reason, err => { + if (err) { } // TODO + }); + })); + }); case 48: return void Env.blobStore.remove.blob(id, Util.both(cb, function (err) { Env.Log.info("REMOVAL_BLOB_BY_ADMIN_RPC", { diff --git a/lib/commands/channel.js b/lib/commands/channel.js index 26dde5103..493ec9e17 100644 --- a/lib/commands/channel.js +++ b/lib/commands/channel.js @@ -172,7 +172,15 @@ Channel.removeOwnedChannel = function (Env, safeKey, obj, __cb, Server) { if (Env.blobStore.isFileId(channelId)) { return void Env.removeOwnedBlob(channelId, safeKey, reason, cb); } - archiveOwnedChannel(Env, safeKey, channelId, reason, cb, Server); + Linked.listLinkedDocuments(Env, channelId, (err, channels) => { + archiveOwnedChannel(Env, safeKey, channelId, reason, (err, data) => { + if (!channels) { return void cb(err, data); } + if (err) { return void cb(err); } + Linked.archiveLinkedData(Env, channelId, reason, channels, () => { + cb(void 0, data); + }); + }, Server); + }); }); }; diff --git a/lib/commands/linked.js b/lib/commands/linked.js index f60fc098c..95156c276 100644 --- a/lib/commands/linked.js +++ b/lib/commands/linked.js @@ -413,5 +413,28 @@ Linked.trimHistory = (Env, data, cb) => { cb(); }); }); - +}; + +// Archive all linked documents that inherit metadata from their +// parent. We consider ownership has already been checked when +// this function is called. +Linked.archiveLinkedData = (Env, channel, reason, channels, _cb) => { + const cb = Util.once(_cb); + let n = nThen; + channels.forEach(chan => { + n = n(w => { + // For each linked document, check if they inherit properties + getMetadata(Env, chan, w((err, md) => { + if (md?.linked !== channel) { return; } + // If they do, archive the document + if (chan.length === HK.BLOB_ID_LENGTH) { + return Env.blobStore.archive.blob(chan, reason, w()); + } + Env.store.archiveChannel(chan, reason, w()); + })); + }).nThen; + }); + n(() => { + cb(); + }); }; diff --git a/lib/commands/metadata.js b/lib/commands/metadata.js index f57e887ef..f218492a7 100644 --- a/lib/commands/metadata.js +++ b/lib/commands/metadata.js @@ -40,7 +40,8 @@ Data.getMetadataRaw = function (Env, channel, _cb, resolveLinked) { Env.checkCache(channel); } - if (resolveLinked && meta?.linked?.length === HK.STANDARD_CHANNEL_LENGTH) { + if (resolveLinked && meta?.linked?.length === HK.STANDARD_CHANNEL_LENGTH + && meta?.linked !== channel) { Data.getMetadataRaw(Env, meta.linked, (err, _meta) => { meta.owners = _meta.owners; meta.restricted = _meta.restricted; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 78061d457..3d9e942fa 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1757,8 +1757,7 @@ define([ var optsPut = { password: newPassword, metadata: { - validateKey: newSecret.keys.validateKey, - linked: newSecret.channel + validateKey: newSecret.keys.validateKey }, }; var optsGet = {