mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Merge branch 'oopinning' into 2025.12-test
This commit is contained in:
commit
beedf2e2f7
13
Dockerfile
13
Dockerfile
@ -3,7 +3,7 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# Multistage build to reduce image size and increase security
|
||||
FROM node:lts-slim AS build
|
||||
FROM node:lts-alpine AS build
|
||||
|
||||
# Create folder for CryptPad
|
||||
RUN mkdir /cryptpad
|
||||
@ -20,19 +20,14 @@ RUN npm install --production \
|
||||
&& npm run install:components
|
||||
|
||||
# Create actual CryptPad image
|
||||
FROM node:lts-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
FROM node:lts-alpine
|
||||
|
||||
# Create user and group for CryptPad so it does not run as root
|
||||
RUN groupadd cryptpad -g 4001
|
||||
RUN useradd cryptpad -u 4001 -g 4001 -d /cryptpad
|
||||
RUN addgroup -S cryptpad -g 4001 && adduser -S cryptpad -G cryptpad --uid 4001 -h /cryptpad
|
||||
|
||||
# Install curl for healthcheck
|
||||
# Install git, rdfind and unzip for install-onlyoffice.sh
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl ca-certificates git rdfind unzip && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --no-cache ca-certificates git rdfind unzip bash curl
|
||||
|
||||
# Copy cryptpad with installed modules
|
||||
COPY --from=build --chown=cryptpad /cryptpad /cryptpad
|
||||
|
||||
@ -295,7 +295,7 @@ install_version() {
|
||||
|
||||
curl "https://github.com/cryptpad/onlyoffice-editor/releases/download/$VERSION/onlyoffice-editor.zip" --location --output "onlyoffice-editor.zip"
|
||||
echo "$HASH onlyoffice-editor.zip" >onlyoffice-editor.zip.sha512
|
||||
if ! sha512sum --check onlyoffice-editor.zip.sha512; then
|
||||
if ! sha512sum -c onlyoffice-editor.zip.sha512; then
|
||||
echo "onlyoffice-editor.zip does not match expected checksum"
|
||||
exit 1
|
||||
fi
|
||||
@ -338,7 +338,7 @@ install_x2t() {
|
||||
ensure_command_available unzip
|
||||
curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/$VERSION/x2t.zip" --location --output x2t.zip
|
||||
echo "$HASH x2t.zip" >x2t.zip.sha512
|
||||
if ! sha512sum --check x2t.zip.sha512; then
|
||||
if ! sha512sum -c x2t.zip.sha512; then
|
||||
echo "x2t.zip does not match expected checksum"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@ -1198,7 +1198,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.32",
|
||||
"version": "2.9.19",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@ -1418,7 +1420,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001763",
|
||||
"version": "1.0.30001766",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
|
||||
"integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@ -1435,6 +1435,17 @@ const factory = (UserObject, Util, Hash,
|
||||
});
|
||||
};
|
||||
|
||||
const getMissingRtChannel = (Env) => {
|
||||
const userObjects = _getUserObjects(Env);
|
||||
const all = [];
|
||||
userObjects.forEach(function (uo) {
|
||||
const missing = uo.getMissingRtChannel();
|
||||
if (!missing) { return; }
|
||||
all.push(missing);
|
||||
});
|
||||
return all;
|
||||
};
|
||||
|
||||
var create = function (proxy, data, uoConfig) {
|
||||
var Env = {
|
||||
pinPads: data.pin,
|
||||
@ -1501,7 +1512,9 @@ const factory = (UserObject, Util, Hash,
|
||||
findFile: callWithEnv(findFile),
|
||||
getEditHash: callWithEnv(getEditHash),
|
||||
user: Env.user,
|
||||
folders: Env.folders
|
||||
folders: Env.folders,
|
||||
// Fix rtChannel
|
||||
getMissingRtChannel: callWithEnv(getMissingRtChannel)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -34,6 +34,8 @@ const factory = (Util, Hash, Realtime) => {
|
||||
|
||||
var debug = exp.debug;
|
||||
|
||||
let missingRtChannel = {};
|
||||
|
||||
exp._setReadOnly = function (state) {
|
||||
readOnly = state;
|
||||
if (!readOnly) { exp.fixFiles(); }
|
||||
@ -883,6 +885,10 @@ const factory = (Util, Hash, Realtime) => {
|
||||
// toClean.push(id);
|
||||
}
|
||||
|
||||
if (['sheet', 'doc', 'presentation'].includes(parsed.type) && !el.rtChannel) {
|
||||
missingRtChannel[el.channel] = el;
|
||||
}
|
||||
|
||||
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
|
||||
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
|
||||
var newName = Hash.createChannelId();
|
||||
@ -991,6 +997,14 @@ const factory = (Util, Hash, Realtime) => {
|
||||
debug("File system was clean.", ms);
|
||||
};
|
||||
|
||||
exp.getMissingRtChannel = () => {
|
||||
const channels = missingRtChannel;
|
||||
missingRtChannel = {};
|
||||
if (readOnly) { return; }
|
||||
if (!Object.keys(channels).length) { return; }
|
||||
return channels;
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
const factory = (Sortify, UserObject, ProxyManager,
|
||||
Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad, Rpc, Cache,
|
||||
Realtime, Messaging, Pinpad, Rpc, Cryptget, Cache,
|
||||
SF, AccountTS, DriveTS, PadTS, Cursor,
|
||||
Support, Integration, OnlyOffice,
|
||||
Mailbox, Profile, Team, Messenger, History,
|
||||
@ -2231,6 +2231,62 @@ const factory = (Sortify, UserObject, ProxyManager,
|
||||
/////////////////////// Init /////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
Store.fixMissingRtChannel = (cb) => {
|
||||
const all = {};
|
||||
const addMissing = (missing) => {
|
||||
missing.forEach(obj => {
|
||||
Object.keys(obj).forEach(chan => {
|
||||
let proxy = obj[chan];
|
||||
let href = proxy.roHref || proxy.href;
|
||||
all[chan] ||= {
|
||||
href,
|
||||
password: proxy.password,
|
||||
list: []
|
||||
};
|
||||
all[chan].list.push(proxy);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const mine = store.manager.getMissingRtChannel();
|
||||
addMissing(mine);
|
||||
const teamsId = store.modules.team.getTeams();
|
||||
teamsId.forEach(id => {
|
||||
const team = store.modules.team.getTeam(id);
|
||||
const teamMissing = team.manager.getMissingRtChannel();
|
||||
addMissing(teamMissing);
|
||||
});
|
||||
|
||||
if (Object.keys(all).length) {
|
||||
Feedback.send('MISSING_RT_CHANNEL', true);
|
||||
}
|
||||
|
||||
let n = nThen;
|
||||
const opts = {
|
||||
network: store.network
|
||||
};
|
||||
Object.keys(all).forEach(chan => {
|
||||
n = n(waitFor => {
|
||||
const data = all[chan];
|
||||
const href = data.href;
|
||||
opts.password = data.password;
|
||||
const parsed = Hash.parsePadUrl(href);
|
||||
Cryptget.get(parsed.hash, waitFor((err, content) => {
|
||||
if (err) { return; }
|
||||
const p = Util.tryParse(content);
|
||||
const rtChannel = p?.content?.channel;
|
||||
if (!rtChannel) { return; }
|
||||
data.list.forEach(proxy => {
|
||||
proxy.rtChannel = rtChannel;
|
||||
});
|
||||
}), opts);
|
||||
}).nThen;
|
||||
});
|
||||
n(() => {
|
||||
setTimeout(cb);
|
||||
});
|
||||
};
|
||||
|
||||
Store.refreshDriveUI = function () {
|
||||
getAllStores().forEach(function (_s) {
|
||||
var send = _s.id ? _s.sendEvent : sendDriveEvent;
|
||||
@ -2384,6 +2440,8 @@ const factory = (Sortify, UserObject, ProxyManager,
|
||||
// Make teams non-blocking
|
||||
if (store.modules['team']) { store.modules['team'].onReady(waitFor); }
|
||||
});
|
||||
}).nThen(function (waitFor) {
|
||||
Store.fixMissingRtChannel(waitFor());
|
||||
}).nThen(function () {
|
||||
var requestLogin = function () {
|
||||
broadcast([], "REQUEST_LOGIN");
|
||||
@ -2983,6 +3041,7 @@ module.exports = factory(
|
||||
require('./components/messaging'),
|
||||
require('../common/pinpad'),
|
||||
require('../common/rpc'),
|
||||
require('../common/cryptget'),
|
||||
require('../common/cache-store'),
|
||||
require('./components/sharedfolder'),
|
||||
require('./components/account'), // .ts
|
||||
|
||||
@ -93,7 +93,7 @@ const _setMetadata: Callback = (ctx, clientId, data, cb) => {
|
||||
otherChannels.forEach(chan => {
|
||||
var _d = Util.clone(data);
|
||||
_d.channel = chan;
|
||||
Store.setPadMetadata(clientId, _d, () => {});
|
||||
s.rpc.setMetadata(_d, () => { });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -915,6 +915,7 @@ define([
|
||||
href: href,
|
||||
title: data.title,
|
||||
owners: optsPut.owners,
|
||||
attributes: common?.otherPadAttrs || {},
|
||||
path: ['template']
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { return void cb(obj.error); }
|
||||
@ -1170,7 +1171,7 @@ define([
|
||||
|
||||
// Make sure we also store additionnal data to pin all the channels
|
||||
// (OO and forms)
|
||||
data.attributes = {};
|
||||
data.attributes ||= {};
|
||||
Object.keys(common?.otherPadAttrs || {}).forEach(k => {
|
||||
data.attributes[k] = common.otherPadAttrs[k];
|
||||
});
|
||||
@ -1197,7 +1198,7 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
let attributes = {};
|
||||
let attributes = data.attributes || {};
|
||||
nThen(function (waitFor) {
|
||||
if (parsed.hashData.type !== 'pad') { return; }
|
||||
// Set the correct owner and expiration time if we can find them
|
||||
|
||||
@ -4940,6 +4940,12 @@ define([
|
||||
opts.noExpiration = true;
|
||||
}
|
||||
|
||||
let attr = opts.attributes = {};
|
||||
if (data.rtChannel) { attr.rtChannel = data.rtChannel; }
|
||||
if (data.lastVersion) { attr.lastVersion = data.lastVersion; }
|
||||
if (data.lastCpHash) { attr.lastCpHash = data.lastCpHash; }
|
||||
if (data.answersChannel) { attr.answersChannel = data.answersChannel; }
|
||||
|
||||
Access.getAccessModal(common, opts, cb);
|
||||
};
|
||||
|
||||
|
||||
@ -18,6 +18,25 @@ define([
|
||||
Messages, nThen, Icons) {
|
||||
var Access = {};
|
||||
|
||||
const getOtherChans = (priv, opts) => {
|
||||
// "attributes" contains the additional channels that the other user
|
||||
// has to store (rtChannel, answersChannels, lastVersion, lastCpHash)
|
||||
// - opts.attributes when access modal is created from the drive
|
||||
// - liveAttr when access modal is created from the document
|
||||
let liveAttr = Util.clone(priv.propChannels || {});
|
||||
delete liveAttr.channel;
|
||||
let attributes = opts.attributes || liveAttr;
|
||||
|
||||
// "otherChan" contains the list of channels that should also receive
|
||||
// the ownership changes
|
||||
let otherChan = [];
|
||||
if (attributes?.answersChannel) { otherChan.push(attributes.answersChannel); }
|
||||
if (attributes?.rtChannel) { otherChan.push(attributes.rtChannel); }
|
||||
if (!otherChan.length) { otherChan = undefined; }
|
||||
|
||||
return { otherChan, attributes };
|
||||
};
|
||||
|
||||
var getOwnersTab = function (Env, data, opts, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var common = Env.common;
|
||||
@ -37,13 +56,10 @@ define([
|
||||
var teamOwner = data.teamId;
|
||||
var title = opts.title;
|
||||
|
||||
var p = priv.propChannels;
|
||||
var otherChan;
|
||||
if (p && p.answersChannel) {
|
||||
otherChan = [p.answersChannel];
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
const { attributes, otherChan } = getOtherChans(priv, opts);
|
||||
|
||||
var redrawAll = function () {};
|
||||
|
||||
var addBtn = h('button.btn.btn-primary.cp-access-add', [Icons.get('arrow-left'), Icons.get('arrow-up')]);
|
||||
@ -337,6 +353,7 @@ define([
|
||||
: Messages.error;
|
||||
return void UI.warn(text);
|
||||
}
|
||||
data.attributes = attributes;
|
||||
sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
|
||||
if (err || (res && res.error)) {
|
||||
return void console.error(err || res.error);
|
||||
@ -354,6 +371,7 @@ define([
|
||||
if (!friend) { return; }
|
||||
common.mailbox.sendTo("ADD_OWNER", {
|
||||
channel: data.channel || priv.channel,
|
||||
attributes,
|
||||
channels: otherChan,
|
||||
href: href,
|
||||
calendar: opts.calendar,
|
||||
@ -440,11 +458,7 @@ define([
|
||||
var allowed = data.allowed || [];
|
||||
var teamOwner = data.teamId;
|
||||
|
||||
var p = priv.propChannels;
|
||||
var otherChan;
|
||||
if (p && p.answersChannel) {
|
||||
otherChan = [p.answersChannel];
|
||||
}
|
||||
const { otherChan } = getOtherChans(priv, opts);
|
||||
|
||||
var redrawAll = function () {};
|
||||
|
||||
@ -854,6 +868,8 @@ define([
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
|
||||
const { otherChan } = getOtherChans(priv, opts);
|
||||
|
||||
var $div = $(h('div.cp-share-columns'));
|
||||
|
||||
if (priv.offline) {
|
||||
@ -1069,13 +1085,15 @@ define([
|
||||
if (err || (obj && obj.error)) { UI.warn(Messages.error); }
|
||||
});
|
||||
|
||||
// If this is a form wiht a answer channel, delete it too
|
||||
var p = priv.propChannels;
|
||||
if (p && p.answersChannel) {
|
||||
sframeChan.query('Q_DELETE_OWNED', {
|
||||
teamId: typeof(owned) !== "boolean" ? owned : undefined,
|
||||
channel: p.answersChannel
|
||||
}, function () {});
|
||||
// If this is a form with an answer channel or an office
|
||||
// doc with an rt channel, delete it too
|
||||
if (otherChan) {
|
||||
otherChan.forEach(chan => {
|
||||
sframeChan.query('Q_DELETE_OWNED', {
|
||||
teamId: typeof(owned) !== "boolean" ? owned : undefined,
|
||||
channel: chan
|
||||
}, function () {});
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!opts.noEditPassword) { $d.append(h('br')); }
|
||||
|
||||
@ -723,7 +723,7 @@ define([
|
||||
if (window.sendCredentials) { xhr.withCredentials = true; }
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = function () {
|
||||
if (/^4/.test('' + this.status)) {
|
||||
if (/^[45]/.test('' + this.status)) {
|
||||
reject(this.status);
|
||||
return void console.error('XHR error', this.status);
|
||||
}
|
||||
|
||||
@ -74,9 +74,9 @@ define([
|
||||
|
||||
Cryptpad.getPadAttribute('rtChannel', function (err, res) {
|
||||
// If already stored, don't pin it again
|
||||
channels.rtChannel = data.channel;
|
||||
if (res && res === data.channel) { return; }
|
||||
Cryptpad.pinPads([data.channel], function () {
|
||||
channels.rtChannel = data.channel;
|
||||
Cryptpad.setPadAttribute('rtChannel', data.channel, function () {});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1117,6 +1117,7 @@ define([
|
||||
href: data.href,
|
||||
channel: data.channel,
|
||||
title: data.title,
|
||||
attributes: data.attributes,
|
||||
owners: data.metadata ? data.metadata.owners : data.owners,
|
||||
expire: data.metadata ? data.metadata.expire : data.expire,
|
||||
forceSave: true
|
||||
|
||||
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