Merge branch 'insert-onlyoffice-api' into 2025.3.1-rc

This commit is contained in:
yflory 2025-05-09 15:40:57 +02:00
commit cb77d20a24
31 changed files with 469 additions and 213 deletions

View File

@ -134,7 +134,12 @@ define(req, function(AppConfig, Default, Language) {
}
};
// XXX
Messages.badges_admin = "Instance administrator";
Messages.badges_moderator = "Instance moderator";
Messages.badges_premium = "Premium user";
Messages.badges_error = "Error while validating this user's data";
Messages.profile_badges = "Badges";
return Messages;

View File

@ -0,0 +1,28 @@
@import (reference) "./colortheme-all.less";
@import (reference) "./variables.less";
.badges_vars(
@width: 30px
) {
@badges-width: @width;
}
.badges_main(@width: 30px) {
--LessLoader_require: LessLoader_currentFile();
.badges_vars(@width);
--badges-width: @badges-width;
--badges-font: ceil(@badges-width*2/3);
}
& {
.badges_vars();
i.cp-badge {
width: var(--badges-width);
height: var(--badges-width);
box-sizing: border-box;
border: 1px solid @cryptpad_text_col;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
font-size: var(--badges-font);
}
}

View File

@ -263,6 +263,14 @@
.cp-toolbar-userlist-friend {
padding: 0;
}
.cp-userlist-badge {
i {
color: @cryptpad_color_brand;
}
i.cp-badge-error {
color: @cryptpad_color_warn_red;
}
}
}
}
}

View File

@ -15,6 +15,10 @@ PROPS_FILE="$CONF_DIR"/onlyoffice.properties
declare -A PROPS
main() {
# clean build env in case a previous build has failed
rm -rf "$BUILDS_DIR"
mkdir -p "$CONF_DIR"
load_props
@ -28,14 +32,70 @@ main() {
set_prop oldest_needed_version v1
mkdir -p "$OO_DIR"
install_old_version v1 4f370beb
install_old_version v2b d9da72fd
install_old_version v4 6ebc6938
install_old_version v5 88a356f0
install_old_version v6 abd8a309
install_version v7 v7.3.3.60+10 be3c926d534b0c77aa1ed83b170a38b9e56ebbbfd73a1a968f523d63c5ed69ecdb063fad08740812de82c1e79af7309faf67ffdd81b45451adce539192f3414c
install_version v8 v8.3.0.83+1 11b7e9765976978f51d8c021c65392637400ed8f5f2b6ad5559f096dba5042e365e3640bd5d6dd80327df34f656e259cb30ba6e4701c3cdd20fc668c9519eb55
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
echo "Do you want to install all available OnlyOffice versions? (Y/N)"
read -r install_all
declare -a selected_versions
if [[ "$install_all" =~ ^[Yy] ]]; then
selected_versions=(v1 v2b v4 v5 v6 v7 v8 x2t)
else
echo "Select which versions to install (space-separated), options are:"
echo " v1 v2b v4 v5 v6 v7 v8 x2t"
read -ra selected_versions
fi
for version in "${selected_versions[@]}"; do
case $version in
v1)
set_prop oldest_needed_version v1; install_old_version v1 4f370beb
# We delete 'help' from previous versions as they are useless and take up storage
rm -rf "$OO_DIR/v1/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v1/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v1/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v2b)
install_old_version v2b d9da72fd
rm -rf "$OO_DIR/v2b/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v2b/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v2b/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v4)
install_old_version v4 6ebc6938
rm -rf "$OO_DIR/v4/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v4/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v4/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v5)
install_old_version v5 88a356f0
rm -rf "$OO_DIR/v5/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v5/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v5/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v6)
install_old_version v6 abd8a309
rm -rf "$OO_DIR/v6/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v6/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v6/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v7)
install_version v7 v7.3.3.60+10 be3c926d534b0c77aa1ed83b170a38b9e56ebbbfd73a1a968f523d63c5ed69ecdb063fad08740812de82c1e79af7309faf67ffdd81b45451adce539192f3414c
rm -rf "$OO_DIR/v7/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/spreadsheeteditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/common/main/resources/help/"
# From all the older versions only v7 has 'dictionaries', we remove it for the same reasons
rm -rf "$OO_DIR/v7/dictionaries/"
;;
v8) install_version v8 v8.3.0.83+3 fbf77671ea8b960b6c65fdad025980894dfc9ed0b79d9540951f56c79871d0bde3361f29a35b9653764f7802ec74df01601b6c9961e0f3118988422667563470 ;;
x2t) install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1 ;;
*)
echo "Unknown version: $version"
exit 1
;;
esac
done
rm -rf "$BUILDS_DIR"
@ -157,15 +217,15 @@ EOF
}
ensure_oo_is_downloaded() {
ensure_command_available git
ensure_command_available git
if ! [ -d "$BUILDS_DIR" ]; then
echo "Downloading OnlyOffice..."
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
fi
if [ ${TRUST_REPOSITORY+x} ] || [ "${PROPS[trust_repository]:-no}" == yes ]; then
git config --global --add safe.directory /cryptpad/onlyoffice-conf/onlyoffice-builds.git
fi
if ! [ -d "$BUILDS_DIR" ]; then
echo "Downloading OnlyOffice..."
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
fi
if [ ${TRUST_REPOSITORY+x} ] || [ "${PROPS[trust_repository]:-no}" == yes ]; then
git config --global --add safe.directory /cryptpad/onlyoffice-conf/onlyoffice-builds.git
fi
}
install_old_version() {

View File

@ -309,3 +309,9 @@ Pinning.getDeletedPads = function (Env, channels, cb) {
Pinning.isChannelPinned = function (Env, channel, cb) {
return void cb(void 0, true);
};
Pinning.isPremium = function (Env, userKey, cb) {
const limit = Env.limits[userKey];
return void cb(void 0, !!limit?.plan);
//return void cb(void 0, (limit?.plan && limit.plan !== "custom"));
};

View File

@ -18,6 +18,7 @@ const bodyParser = require('body-parser');
const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const plugins = require("./plugin-manager");
const gzipStatic = require('connect-gzip-static');
const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;
@ -337,7 +338,7 @@ Object.keys(plugins || {}).forEach(name => {
// serve custom app content from the customize directory
// useful for testing pages customized with opengraph data
app.use(Express.static(Path.resolve('./customize/www')));
app.use(Express.static(Path.resolve('./www')));
app.use(gzipStatic(Path.resolve('./www')));
var mainPages = Env.mainPages || Default.mainPages();
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');

View File

@ -24,6 +24,7 @@ const UNAUTHENTICATED_CALLS = {
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage,
DELETE_MAILBOX_MESSAGE: Channel.deleteMailboxMessage,
GET_METADATA: Metadata.getMetadata,
IS_PREMIUM: Pinning.isPremium,
ADD_FIRST_ADMIN: Admin.addFirstAdmin
};

View File

@ -20,7 +20,6 @@ const map = {
'./src/common/common-credential.js': './www/common/common-credential.js',
'./src/common/common-feedback.js': './www/common/common-feedback.js',
'./src/common/common-hash.js': './www/common/common-hash.js',
'./src/common/common-messaging.js': './www/common/common-messaging.js',
'./src/common/common-realtime.js': './www/common/common-realtime.js',
'./src/common/common-signing-keys.js': './www/common/common-signing-keys.js',
'./src/common/common-util.js': './www/common/common-util.js',

17
package-lock.json generated
View File

@ -23,6 +23,7 @@
"ckeditor": "npm:ckeditor4@~4.22.1",
"codemirror": "^5.19.0",
"components-font-awesome": "^4.6.3",
"connect-gzip-static": "^4.2.1",
"cookie-parser": "^1.4.7",
"croppie": "^2.5.0",
"dragula": "3.7.2",
@ -2066,6 +2067,22 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/connect-gzip-static": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/connect-gzip-static/-/connect-gzip-static-4.2.1.tgz",
"integrity": "sha512-6qC1NYUEZU6mftg2gPspcoPjgBzADm+fB1fPXu3RxBVe59P/Q9HQGQEHnuDaGRDJUoJwl5NX2OgoaaZp7RdpfQ==",
"license": "MIT",
"dependencies": {
"debug": "~2||~3||~4",
"mime-types": "~2",
"parseurl": "~1",
"send": "~0 || ~1",
"serve-static": "~1 || ~2"
},
"engines": {
"node": ">= 20.1.0"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",

View File

@ -26,6 +26,7 @@
"ckeditor": "npm:ckeditor4@~4.22.1",
"codemirror": "^5.19.0",
"components-font-awesome": "^4.6.3",
"connect-gzip-static": "^4.2.1",
"cookie-parser": "^1.4.7",
"croppie": "^2.5.0",
"dragula": "3.7.2",

View File

@ -9,7 +9,7 @@ const factory = (Sortify, UserObject, ProxyManager,
SF, AccountTS, DriveTS, Cursor,
Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
Calendar, Block, NetConfig,
Calendar, BadgeTS, Block, NetConfig,
Crypto, ChainPad, CpNetflux, Listmap,
Netflux, nThen) => {
@ -17,6 +17,7 @@ const factory = (Sortify, UserObject, ProxyManager,
let AppConfig = {};
const Account = AccountTS.Account;
const Drive = DriveTS.Drive;
const Badge = BadgeTS.Badge;
const window = globalThis;
globalThis.nacl = globalThis.nacl || Crypto.Nacl;
@ -661,11 +662,15 @@ const factory = (Sortify, UserObject, ProxyManager,
color: Store.getUserColor(),
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
curvePublic: proxy.curvePublic,
edPublic: proxy.edPublic,
netfluxId: store?.network?.webChannels?.[0]?.myID,
badge: Util.find(proxy, ['profile', 'badge'])
},
// "priv" is not shared with other users but is needed by the apps
priv: {
clientId: clientId,
edPublic: proxy.edPublic,
edPrivate: proxy.edPrivate,
friends: proxy.friends || {},
settings: proxy.settings || NEW_USER_SETTINGS,
thumbnails: disableThumbnails === false,
@ -2809,6 +2814,7 @@ const factory = (Sortify, UserObject, ProxyManager,
loadUniversal(Integration, 'integration', waitFor);
loadUniversal(Messenger, 'messenger', waitFor);
loadUniversal(History, 'history', waitFor);
loadUniversal(Badge, 'badge', waitFor);
loadOnlyOffice();
if (store) {
store.messenger = store.modules['messenger'];
@ -3471,7 +3477,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../common/common-constants'),
require('../common/common-feedback'),
require('../common/common-realtime'),
require('../common/common-messaging'),
require('./components/messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('./components/merge-drive'),
@ -3489,6 +3495,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('./modules/messenger'),
require('./modules/history'),
require('./modules/calendar'),
require('./modules/badge'),
require('../common/login-block'),
require('../common/network-config'),
require('chainpad-crypto'),

View File

@ -985,7 +985,7 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-messaging'),
require('./messaging'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('chainpad-crypto'),
@ -993,7 +993,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-messaging.js',
'/common/outer/messaging.js',
'/common/common-hash.js',
'/common/common-util.js',
'/components/chainpad-crypto/crypto.js',

View File

@ -15,6 +15,7 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
curvePublic: proxy.curvePublic,
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar,
badge: proxy.profile && proxy.profile.badge,
uid: proxy.uid,
};
if (hash === false) { delete data.channel; }
@ -142,10 +143,10 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('chainpad-crypto'),
require('./common-hash'),
require('./common-util'),
require('./common-constants'),
require('./common-realtime')
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-constants'),
require('../../common/common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([

View File

@ -512,7 +512,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../../common/common-feedback'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-messaging'),
require('./messaging'),
require('../../common/cryptget'),
require('../modules/mailbox'),
undefined,
@ -525,7 +525,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-messaging.js',
'/common/outer/messaging.js',
'/common/cryptget.js',
'/common/outer/mailbox.js',
'/customize/messages.js',

114
src/worker/modules/badge.ts Normal file
View File

@ -0,0 +1,114 @@
import nacl from 'tweetnacl/nacl-fast';
import nThen from 'nthen';
import { Module, ModuleObject, Callback } from '../types'
import * as Util from '../../common/common-util.js';
export interface BadgeModule<T> extends Module<T> {
setCustomize: (data: any) => void
}
let ApiConfig:any = {};
const checkApi = (ctx, ed, cb) => {
const badges = [];
const origin:string = ApiConfig?.httpUnsafeOrigin;
Util.fetchApi(origin, 'config', true, Config => {
const isAdmin = Config?.adminKeys?.includes(ed);
if (isAdmin) { badges.push('admin'); }
const isModerator = Config?.moderatorKeys?.includes(ed);
if (isModerator) { badges.push('moderator'); }
cb(badges);
});
};
const checkPremium = (ctx, ed, cb) => {
ctx.Store.anonRpcMsg('', {
msg: 'IS_PREMIUM',
data: ed
}, obj => {
let premium = Array.isArray(obj) && obj[0];
cb(premium ? ['premium'] : []);
});
};
const listBadges:Callback = (ctx, data, clientId, cb) => {
const badges = [];
const origin:string = ApiConfig?.httpUnsafeOrigin;
const ed = data.edPublic || ctx.store?.proxy?.edPublic;
nThen(waitFor => {
checkApi(ctx, ed, waitFor(list => {
Array.prototype.push.apply(badges, list);
}));
}).nThen(waitFor => {
checkPremium(ctx, ed, waitFor(list => {
Array.prototype.push.apply(badges, list);
}));
}).nThen(() => {
cb(badges);
});
};
const allBadges = {
'admin': checkApi,
'moderator': checkApi,
'premium': checkPremium
};
const checkBadge:Callback = (ctx, data, clientId, cb) => {
const { badge, ed, sig, nid/*, channel*/ } = data;
const publicKey = Util.decodeBase64(ed); // Public key uint8
const signature = Util.decodeBase64(sig); // badge uint8
// Check signature
const msg = nacl.sign.open(signature, publicKey);
if (!msg) { return void cb({verified: false}); }
// Check channel (replay attack)
const msgStr:string = Util.encodeUTF8(msg);
const expected:string = nid; // + channel;
if (msgStr !== expected) { return void cb({verified: false}); }
let f = allBadges[badge];
if (!f) { return void cb({verified: false, error: 'EINVAL'}); }
f(ctx, ed, list => {
cb({
verified: list.includes(badge),
badge: badge
});
});
};
const Badge: BadgeModule<ModuleObject> = {
init: (config, cb, emit) => {
const ctx:any = {
store: config.store,
Store: config.Store,
updateMetadata: config.updateMetadata
};
return {
removeClient: () => {},
execCommand: (clientId, obj, cb) => {
console.log('Exex command', clientId, obj);
const cmd = obj.cmd;
const data = obj.data;
if (cmd === 'LIST_BADGES') {
return void listBadges(ctx, data, clientId, cb);
}
if (cmd === 'CHECK_BADGE') {
return void checkBadge(ctx, data, clientId, cb);
}
cb();
},
}
},
setCustomize: data => {
ApiConfig = data?.ApiConfig;
}
};
export { Badge }

View File

@ -679,7 +679,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../components/messaging'),
require('../../common/notify'),
require('../components/mailbox-handlers'),
require('chainpad-netflux'),
@ -691,7 +691,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/outer/messaging.js',
'/common/notify.js',
'/common/outer/mailbox-handlers.js',
'chainpad-netflux',

View File

@ -1126,7 +1126,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../components/messaging'),
require('../../common/common-constants'),
undefined,
require('../../common/pad-types'),
@ -1138,7 +1138,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/outer/messaging.js',
'/common/common-constants.js',
'/customize/messages.js',
'/common/pad-types.js',

View File

@ -99,6 +99,12 @@ const factory = (Util, Hash, Constants, Realtime,
ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients.filter(function (clientId) {
return clientId !== cId;
}));
if (key === 'badge') {
ctx.Store.set(null, {
key: ['profile', 'badge'],
value: value || undefined
}, () => {});
}
cb(ctx.listmap.proxy);
});
};
@ -113,6 +119,7 @@ const factory = (Util, Hash, Constants, Realtime,
var store = cfg.store;
if (!store.loggedIn || !store.proxy.edPublic) { return; }
var ctx = {
Store: cfg.Store,
store: store,
pinPads: cfg.pinPads,
updateMetadata: cfg.updateMetadata,

View File

@ -2253,7 +2253,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../../common/user-object'),
require('../components/sharedfolder'),
require('../components/roster'),
require('../../common/common-messaging'),
require('../components/messaging'),
require('../../common/common-feedback'),
require('../components/invitation'),
require('../../common/cryptget'),
@ -2278,7 +2278,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/user-object.js',
'/common/outer/sharedfolder.js',
'/common/outer/roster.js',
'/common/common-messaging.js',
'/common/outer/messaging.js',
'/common/common-feedback.js',
'/common/outer/invitation.js',
'/common/cryptget.js',

View File

@ -12,7 +12,7 @@ export interface TestModuleObject extends ModuleObject {
const TestModule: Module<TestModuleObject> = {
init: (config, cb) => {
init: (config, cb, emit) => {
let myTest = {};
cb();

View File

@ -15,7 +15,6 @@ import * as Util from '../common/common-util.js';
import * as Hash from '../common/common-hash.js';
import * as Feedback from '../common/common-feedback.js';
import * as Realtime from '../common/common-realtime.js';
import * as Messaging from '../common/common-messaging.js';
import * as Constants from '../common/common-constants.js';
import * as Credential from '../common/common-credential.js';
import * as ProxyManager from '../common/proxy-manager.js';
@ -36,6 +35,7 @@ import * as AsyncConnector from './core/async-connector.js';
// Components
import * as Migrate from './components/migrate-user-object.js';
import * as Messaging from './components/messaging.js';
// Modules
import * as Mailbox from './modules/mailbox.js';
@ -48,6 +48,7 @@ import * as Team from './modules/team.js';
import * as Messenger from './modules/messenger.js';
import * as History from './modules/history.js';
import * as Calendar from './modules/calendar.js';
import { Badge } from './modules/badge';
declare var WorkerGlobalScope: any
declare var SharedWorkerGlobalScope: any
@ -76,7 +77,8 @@ let start = (cfg: StoreConfig):void => {
Calendar,
Store,
Account,
Mailbox
Mailbox,
Badge
].forEach(dep => {
if (typeof(dep.setCustomize) === "function") {
dep.setCustomize(cfg);

View File

@ -7,7 +7,11 @@
export type Callback = (...args: any[]) => void
export type ModuleConfig = {
emit: Function
store: any,
Store: any,
updateMetadata: Callback,
pinPads: Callback,
unpinPads: Callback
}
export type CommandData = {
@ -21,7 +25,7 @@ export interface ModuleObject {
}
export interface Module<T> {
init: (config: ModuleConfig, cb: Callback) => T
init: (config: ModuleConfig, cb: Callback, emit: Callback) => T
}

View File

@ -1,161 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
var Msg = {};
var createData = Msg.createData = function (proxy, hash) {
var data = {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar,
uid: proxy.uid,
};
if (hash === false) { delete data.channel; }
return data;
};
var getFriend = Msg.getFriend = function (proxy, pubkey) {
if (!pubkey) { return; }
if (pubkey === proxy.curvePublic) {
var data = createData(proxy);
delete data.channel;
return data;
}
return proxy.friends ? proxy.friends[pubkey] : undefined;
};
var getFriendList = Msg.getFriendList = function (proxy) {
if (!proxy.friends) { proxy.friends = {}; }
return proxy.friends;
};
var eachFriend = function (friends, cb) {
Object.keys(friends).forEach(function (id) {
if (id === 'me') { return; }
cb(friends[id], id, friends);
});
};
Msg.getFriendChannelsList = function (proxy) {
var list = [];
eachFriend(proxy.friends, function (friend) {
list.push(friend.channel);
});
return list;
};
Msg.declineFriendRequest = function (store, data, cb) {
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, {
channel: data.notifications,
curvePublic: data.curvePublic
}, function (obj) {
cb(obj);
});
};
Msg.acceptFriendRequest = function (store, data, cb) {
var friend = getFriend(store.proxy, data.curvePublic) || {};
var myData = createData(store.proxy, friend.channel || data.channel);
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', { user: myData }, {
channel: data.notifications,
curvePublic: data.curvePublic
}, function (obj) {
cb(obj);
});
};
Msg.addToFriendList = function (cfg, data, cb) {
var proxy = cfg.proxy;
var friends = getFriendList(proxy);
var pubKey = data.curvePublic; // todo validata data
if (pubKey === proxy.curvePublic) { return void cb("E_MYKEY"); }
friends[pubKey] = data;
Realtime.whenRealtimeSyncs(cfg.realtime, function () {
cb();
cfg.pinPads([data.channel], function (res) {
if (res.error) { console.error(res.error); }
});
});
};
Msg.updateMyData = function (store, curve) {
var myData = createData(store.proxy, false);
if (store.proxy.friends) {
store.proxy.friends.me = Util.clone(myData);
delete store.proxy.friends.me.channel;
}
if (store.modules['team']) {
store.modules['team'].updateMyData(myData);
}
var todo = function (friend) {
if (!friend || !friend.notifications) { return; }
delete friend.user;
myData.channel = friend.channel;
store.mailbox.sendTo('UPDATE_DATA', myData, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, function (obj) {
if (obj && obj.error) { console.error(obj); }
});
};
if (curve) {
var friend = getFriend(store.proxy, curve);
return void todo(friend);
}
eachFriend(store.proxy.friends || {}, todo);
};
Msg.removeFriend = function (store, curvePublic, cb) {
var proxy = store.proxy;
var friend = proxy.friends[curvePublic];
if (!friend) { return void cb({error: 'ENOENT'}); }
if (!friend.notifications) { return void cb({error: 'EINVAL'}); }
store.mailbox.sendTo('UNFRIEND', {
curvePublic: proxy.curvePublic
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, function (obj) {
if (obj && obj.error) {
return void cb(obj);
}
store.messenger.onFriendRemoved(curvePublic, friend.channel);
delete proxy.friends[curvePublic];
Realtime.whenRealtimeSyncs(store.realtime, function () {
cb(obj);
});
});
};
return Msg;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('chainpad-crypto'),
require('./common-hash'),
require('./common-util'),
require('./common-constants'),
require('./common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -9,7 +9,6 @@ define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/outer/cache-store.js',
'/common/common-messaging.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/visible.js',
@ -25,7 +24,7 @@ define([
'/components/nthen/index.js',
'/components/tweetnacl/nacl-fast.min.js'
], function (Config, Broadcast, Messages, Util, Hash, Cache,
Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Cred, Login, Store, AppConfig, nThen) {
/* This file exposes functionality which is specific to Cryptpad, but not to

View File

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'/common/hyperscript.js',
'/customize/messages.js',
], function (h, Messages) {
var Badges = {};
const badges = {
admin: 'fa-star',
moderator: 'fa-life-ring',
premium: 'fa-check-circle',
error: 'fa-exclamation-circle'
};
Badges.render = id => {
let icon = badges[id];
if (!icon) { return; }
let cls = icon.indexOf('cptools') === 0 ? 'cptools '+icon : 'fa '+icon;
if (id === 'error') { cls += ' cp-badge-error'; }
return h('i', {
'class': `cp-badge ${cls}`,
'title': Messages[`badges_${id}`] || id
});
};
return Badges;
});

View File

@ -186,7 +186,7 @@ define([
if (avatars[href]) {
var nodes = $.parseHTML(avatars[href]);
var $el = $(nodes[0]);
$container.append($el);
$container.empty().append($el);
return void cb($el);
}
@ -222,7 +222,7 @@ define([
if (typeof data !== "number") { return void displayDefault(); }
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
var mt = UI.mediaTag(src, cryptKey);
var $img = $(mt).appendTo($container);
var $img = $(mt).appendTo($container.empty());
MT.displayMediatagImage(common, $img, function (err, $image) {
if (err) { return void console.error(err); }
centerImage($img, $image);

View File

@ -751,6 +751,7 @@ define([
if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
var edPublic, curvePublic, notifications, isTemplate;
var edPrivate;
var settings = {};
var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1;
var isOO = ['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1;
@ -760,6 +761,7 @@ define([
if (isDeleted) {
Utils.Cache.clearChannel(secret.channel);
}
let signature;
var updateMeta = function () {
//console.log('EV_METADATA_UPDATE');
@ -775,6 +777,9 @@ define([
curvePublic = metaObj.user.curvePublic;
notifications = metaObj.user.notifications;
settings = metaObj.priv.settings;
edPrivate = metaObj.priv.edPrivate;
delete metaObj.priv.edPrivate; // don't send to inner
}));
if (typeof(isTemplate) === "undefined") {
Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) {
@ -856,6 +861,18 @@ define([
}
}
if (metaObj?.user?.edPublic) {
let str = metaObj?.user?.netfluxId;// + secret.channel;
if (!signature && str) {
let myIDu8 = Utils.Util.decodeUTF8(str);
let k = Utils.Util.decodeBase64(edPrivate);
let nacl = Utils.Crypto.Nacl;
let s = nacl.sign(myIDu8, k);
signature = Utils.Util.encodeBase64(s);
}
metaObj.user.signature = signature;
}
// Early access
var priv = metaObj.priv;
var _plan = typeof(priv.plan) === "undefined" ? Utils.LocalStore.getPremium() : priv.plan;

View File

@ -13,12 +13,13 @@ define([
'/common/common-util.js',
'/common/common-feedback.js',
'/common/inner/common-mediatag.js',
'/common/inner/badges.js',
'/common/hyperscript.js',
'/common/messenger-ui.js',
'/customize/messages.js',
'/customize/pages.js',
'/common/pad-types.js',
], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, h,
], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, Badges, h,
MessengerUI, Messages, Pages, PadTypes) {
var Common;
@ -193,16 +194,14 @@ MessengerUI, Messages, Pages, PadTypes) {
// Display only one time each user (if he is connected in multiple tabs)
var uids = [];
Object.keys(userData).forEach(function(user) {
//if (user !== userNetfluxId) {
var data = userData[user] || {};
var userId = data.uid;
if (!userId) { return; }
//data.netfluxId = user;
if (uids.indexOf(userId) === -1) {// && (!myUid || userId !== myUid)) {
if (user !== data.netfluxId) { return; }
if (uids.indexOf(userId) === -1) {
uids.push(userId);
list.push(data);
} else { i++; }
//}
});
return {
list: list,
@ -223,6 +222,7 @@ MessengerUI, Messages, Pages, PadTypes) {
});
};
var showColors = false;
const validatedBadges = {};
var updateUserList = function (toolbar, config, forceOffline) {
if (!config.displayed || config.displayed.indexOf('userlist') === -1) { return; }
if (toolbar.isAlone) { return; }
@ -427,6 +427,38 @@ MessengerUI, Messages, Pages, PadTypes) {
$span.append($rightCol);
}, data.uid);
$span.data('uid', data.uid);
if (data.badge && data.edPublic) {
const addBadge = (badge) => {
let i = Badges.render(badge);
if (!i) { return; }
$rightCol.append(h('div.cp-userlist-badge', i));
};
const key = data.signature + '-' + data.badge;
const v = validatedBadges[key];
if (typeof (v) === "string") {
addBadge(v);
} else if (v === false) {
addBadge('error');
} else {
let ev = validatedBadges[key] ||= Util.mkEvent(true);
ev.reg(badge => { addBadge(badge); });
toolbar.badges.execCommand('CHECK_BADGE', {
badge: data.badge,
channel: priv.channel,
ed: data.edPublic,
sig: data.signature,
nid: data.netfluxId
}, res => {
if (!res?.verified) {
validatedBadges[key] = false;
return void addBadge('error');
}
validatedBadges[key] = res.badge;
ev.fire(res.badge);
});
}
}
$editUsersList.append($span);
});
@ -1401,6 +1433,8 @@ MessengerUI, Messages, Pages, PadTypes) {
toolbar.connected = false;
toolbar.firstConnection = true;
toolbar.badges = Common.makeUniversal('badge');
if (Array.isArray(cfg.displayed) && cfg.displayed.includes('pad')) {
cfg.addFileMenu = true;
}

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (reference) '../../customize/src/less2/include/variables.less';
@import (reference) '../../customize/src/less2/include/badges.less';
&.cp-app-profile {
@ -26,6 +27,29 @@
margin-top: 1rem;
}
.cp-profile-badges {
display: flex;
align-items: center;
& > span {
margin-right: 1em;
}
.cp-profile-badges-list {
.badges_main(40px);
i {
cursor: pointer;
&:focus-visible {
outline: @variables_focus_style;
}
&:not(:last-child) {
margin-right: 0.5em;
}
&.cp-selected {
border-color: @cryptpad_color_brand;
}
}
}
}
#cp-app-profile-header {
display: flex;
flex-flow: row wrap;

View File

@ -17,6 +17,7 @@ define([
'/common/common-realtime.js',
'/common/clipboard.js',
'/common/inner/common-mediatag.js',
'/common/inner/badges.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/customize/application_config.js',
@ -48,6 +49,7 @@ define([
Realtime,
Clipboard,
MT,
Badges,
h,
Messages,
AppConfig,
@ -92,6 +94,7 @@ define([
var LINK_ID = "cp-app-profile-link";
var AVATAR_ID = "cp-app-profile-avatar";
var DESCRIPTION_ID = "cp-app-profile-description";
var BADGES_ID = "cp-app-profile-badges";
var CREATE_ID = "cp-app-profile-create";
var HEADER_ID = "cp-app-profile-header";
var HEADER_RIGHT_ID = "cp-app-profile-rightside";
@ -375,7 +378,7 @@ define([
var displayAvatar = function (val) {
var sframeChan = common.getSframeChannel();
var $span = APP.$avatar;
$span.html('');
$span.empty();
if (!val) {
$('<img>', {
src: '/customize/images/avatar.png',
@ -447,6 +450,48 @@ define([
displayAvatar(data.avatar);
};
const addBadges = $container => {
var $block = $('<div>', {id: BADGES_ID, class:'cp-sidebarlayout-element'}).appendTo($container);
APP.$badges = $(h('span')).appendTo($block);
};
const refreshBadges = (obj) => {
if (!APP.$badges) { return; }
const metadataMgr = APP.common.getMetadataMgr();
const privateData = metadataMgr.getPrivateData();
let args = {};
if (!privateData.isOwnProfile) { args.edPublic = obj.edPublic; }
APP.badge.execCommand('LIST_BADGES', args, data => {
APP.$badges.empty();
let spinner;
APP.$badges.toggle(!!data.length);
let all = data.map(str => {
const i = Badges.render(str);
const $i = $(i).attr('tabindex', 0);
if (APP.readOnly) { return i; }
const selected = obj?.badge === str;
if (selected) { $i.addClass('cp-selected'); }
Util.onClickEnter($i, () => {
let value = selected ? '' : str;
spinner.spin();
APP.module.execCommand('SET', {
key: 'badge',
value
}, function (data) {
spinner.hide();
APP.updateValues(data);
});
});
return i;
});
let content = h('div.cp-profile-badges', [
h('span', Messages.profile_badges),
h('div.cp-profile-badges-list', all)
]);
APP.$badges.append(content);
spinner = UI.makeSpinner(APP.$badges.find('> div'));
});
};
var addDescription = function ($container) {
var $block = $('<div>', {id: DESCRIPTION_ID, class: PROFILE_SECTION}).appendTo($container);
@ -462,7 +507,7 @@ define([
}, [
h('i.fa.fa-pencil', {'aria-hidden': 'true' }),
h('span#cp-profile-add-description-button', Messages.profile_addDescription)
]);
]);
APP.$descriptionEdit = $(button);
var save = h('button.btn.btn-primary', Messages.settings_save);
var text = h('textarea');
@ -609,6 +654,7 @@ define([
addLink($rightside);
addFriendRequest($rightside);
addMuteButton($rightside);
addBadges($rightside);
addPublicKey($rightside);
addCopyData($rightside);
addViewButton($rightside);
@ -627,6 +673,7 @@ define([
refreshName(data);
refreshLink(data);
refreshDescription(data);
refreshBadges(data);
refreshFriendRequest(data);
refreshMute(data);
setPublicKeyButton(data);
@ -700,6 +747,9 @@ define([
return;
}
APP.badge = common.makeUniversal('badge', {
onEvent: onEvent
});
if (privateData.isOwnProfile) {
APP.module = common.makeUniversal('profile', {