Merge branch '2025.3.1-rc' into staging

This commit is contained in:
yflory 2025-05-13 11:11:12 +02:00
commit c276ca758e
39 changed files with 1304 additions and 881 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;
}
}
}
}
}

151
funding.json Normal file
View File

@ -0,0 +1,151 @@
{
"version": "v1.0.0",
"drips": {
"ethereum": {
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
}
},
"entity": {
"type": "organisation",
"role": "owner",
"name": "XWiki SAS",
"email": "funding@xwiki.com",
"phone": "",
"description": "XWiki SAS is a European Open Source organization, maintaining Open Source Collaboration projects, XWiki and CryptPad. We believe that Open Source projects are key to getting back control on software, and that companies are important to allow the maintenance of Open Source projects.",
"webpageUrl": {
"url": "https://xwiki.com",
"wellKnown": "https://xwiki.com/.well-known/funding-manifest-urls"
}
},
"projects": [
{
"guid": "cryptpad",
"name": "CryptPad",
"description": "CryptPad is an end-to-end encrypted Collaboration Platform focused on providing real-time editing tools while protecting privacy.",
"webpageUrl": {
"url": "https://cryptpad.org",
"wellKnown": "https://cryptpad.org/.well-known/funding-manifest-urls"
},
"repositoryUrl": {
"url": "https://github.com/cryptpad/cryptpad"
},
"licenses": [
"spdx:AGPL-3.0-or-later"
],
"tags": [
"privacy",
"e2ee",
"collaboration",
"realtimeediting",
"office",
"drive"
]
}
],
"funding": {
"channels": [
{
"guid": "opencollective-cryptpad",
"type": "payment-provider",
"address": "https://opencollective.com/cryptpad",
"description": "OpenCollective CryptPad Account"
},
{
"guid": "bank",
"type": "bank",
"address": "Contact XWiki SAS",
"description": "Donation by Bank Transfer or other means with XWiki SAS"
}
],
"plans": [
{
"guid": "cryptpad-individual-helper",
"status": "active",
"name": "Individual Donator - Starter",
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n35000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an occasional user of CryptPad\n- a regular user with limited funds\n- somebody just wanting to give us a nudge",
"amount": 20,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad"
]
},
{
"guid": "cryptpad-individual-user",
"status": "active",
"name": "Individual Donator - User",
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n14000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad\n- a privacy believer who would like to help us\n- a personal instance owner with limited funds",
"amount": 50,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad"
]
},
{
"guid": "cryptpad-individual-sponsor",
"status": "active",
"name": "Individual Donator - Sponsor",
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n3500 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad who really likes CryptPad\n- a privacy believer who wants to accelerate privacy online\n- a personal or community small size instance owner",
"amount": 200,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad"
]
},
{
"guid": "cryptpad-instanceowner-smallorg",
"status": "active",
"name": "Small Organization Using CryptPad",
"description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n1500 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a non-profit organization having some funds hosting CryptPad for their members\n- a small company using CryptPad for its users\n- any organizations wanting Privacy software to progress",
"amount": 500,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad"
]
},
{
"guid": "cryptpad-instanceowner-bigorg",
"status": "active",
"name": "Small Organization Using CryptPad",
"description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n350 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad regularly internally or externally\n- any organization having sufficient funds wanting to support Open Source Software doing the right thing\n",
"amount": 2000,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad",
"bank"
]
},
{
"guid": "cryptpad-instanceowner-bigorg",
"status": "active",
"name": "Organization using CryptPad heavily or as part of their offering",
"description": "Organization using CryptPad heavily or as part of it's commercial offering and which would like to significantly contribute to it's development.\n\n70 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad heavily or as part of a commercial offering\n- foundations who would like to support the development.",
"amount": 10000,
"currency": "EUR",
"frequency": "yearly",
"channels": [
"opencollective-cryptpad",
"bank"
]
},
{
"guid": "cryptpad-org-funder",
"status": "active",
"name": "Funding organization or large company supporting the maintenance and development of CryptPad",
"description": "This is meant for an organization investing in Privacy Preserving technologies and/or Open Source software which is important for the internet and wanting to support CryptPad with a grant. The grant can be for specific development useful for our roadmap or just for maintenance of the software.\n\n12 such grants would be needed to fund the current team.",
"amount": 50000,
"currency": "EUR",
"frequency": "one-time",
"channels": [
"opencollective-cryptpad",
"bank"
]
}
],
"history": []
}
}

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
@ -23,18 +27,75 @@ main() {
ask_for_license
# Remeber the 1st version that is installed. This will help us install only
# Remember the 1st version that is installed. This will help us install only
# needed OnlyOffice versions in a later version of this script.
set_prop oldest_needed_version v1
mkdir -p "$OO_DIR"
install_version v1 4f370beb
install_version v2b d9da72fd
install_version v4 6ebc6938
install_version v5 88a356f0
install_version v6 abd8a309
install_version v7 e1267803
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"
@ -156,23 +217,22 @@ 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_version() {
install_old_version() {
local DIR=$1
local COMMIT=$2
local FULL_DIR=$OO_DIR/$DIR
local LAST_DIR
LAST_DIR=$(pwd)
local LAST_DIR=$(pwd)
local ACTUAL_COMMIT="not installed"
if [ -e "$FULL_DIR"/.commit ]; then
@ -206,6 +266,40 @@ install_version() {
fi
}
install_version() {
ensure_command_available curl
ensure_command_available sha512sum
ensure_command_available unzip
local DIR=$1
local VERSION=$2
local HASH=$3
local FULL_DIR=$OO_DIR/$DIR
local LAST_DIR=$(pwd)
if [ ! -e "$FULL_DIR"/.version ] || [ "$(cat "$FULL_DIR"/.version)" != "$VERSION" ]; then
rm -rf "$FULL_DIR"
mkdir -p "$FULL_DIR"
cd "$FULL_DIR"
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
echo "onlyoffice-editor.zip does not match expected checksum"
exit 1
fi
unzip onlyoffice-editor.zip
rm onlyoffice-editor.zip*
echo "$VERSION" >"$FULL_DIR"/.version
echo "$DIR updated"
else
echo "$DIR was up to date"
fi
}
install_x2t() {
local VERSION=$1
local HASH=$2
@ -233,7 +327,6 @@ install_x2t() {
ensure_command_available sha512sum
ensure_command_available unzip
curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/$VERSION/x2t.zip" --location --output x2t.zip
# curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/v7.3%2B1/x2t.zip" --location --output x2t.zip
echo "$HASH x2t.zip" >x2t.zip.sha512
if ! sha512sum --check x2t.zip.sha512; then
echo "x2t.zip does not match expected checksum"

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;
@ -134,6 +135,7 @@ var applyHeaderMap = function (res, map) {
var EXEMPT = [
/^\/common\/onlyoffice\/.*\.html.*/,
/^\/common\/onlyoffice\/dist\/.*\/sdkjs\/common\/spell\/spell\/spell.js.*/, // OnlyOffice loads spell.wasm in a way that needs unsave-eval
/^\/(sheet|presentation|doc)\/inner\.html.*/,
/^\/unsafeiframe\/inner\.html.*$/,
];
@ -336,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',

51
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "cryptpad",
"version": "2025.3.0",
"version": "2025.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cryptpad",
"version": "2025.3.0",
"version": "2025.3.1",
"license": "AGPL-3.0+",
"dependencies": {
"@mcrowe/minibloom": "^0.2.0",
@ -17,12 +17,13 @@
"bootstrap-tokenfield": "^0.12.0",
"chainpad": "^5.2.6",
"chainpad-crypto": "^0.3.0",
"chainpad-listmap": "^1.1.1",
"chainpad-netflux": "^1.2.3",
"chainpad-listmap": "^1.2.0",
"chainpad-netflux": "^1.3.0",
"chainpad-server": "^5.2.4",
"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",
@ -1913,11 +1914,12 @@
}
},
"node_modules/chainpad-listmap": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.1.1.tgz",
"integrity": "sha512-SXb3iJqWyp2wOMa9FT4kqOuR1KgjMPcNCzLlYignu1GdxMI67ZDGTLDucmht4CD8yuYzUWzWmJgs1vs/acH+rw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.2.0.tgz",
"integrity": "sha512-PI7iwk/yKzEb1iaVmLKvMJYXjCjvIgUWzI07LeDGVvZPScnYbEzMso9YMQyxzjqrp8cyQxsL7sA8c/hZAz01cw==",
"license": "AGPL-3.0+",
"dependencies": {
"chainpad-netflux": "^1.2.0",
"chainpad-netflux": "^1.3.0",
"json.sortify": "~2.1.0"
}
},
@ -1930,12 +1932,14 @@
}
},
"node_modules/chainpad-netflux": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.2.3.tgz",
"integrity": "sha512-bXLUSxAsCu4kRnGorW+7fVofiCWSNGRkB+uFJ4kUV/zDJ3RJYU4J3+DZsL4rCTkLkAjgKt+YQCoYciCKLlTwqg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.3.0.tgz",
"integrity": "sha512-oc47vLt78a/Fp7acQJvLNuX1JRrH9oOWAbzo+yOO1asCWDuI4oesPY81wHzVySKpo58GGfNpZN7CSTI8EZjYrQ==",
"license": "LGPL-2.1",
"dependencies": {
"netflux-websocket": "^1.2.0"
"netflux-websocket": "^1.2.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1"
}
},
"node_modules/chainpad-server": {
@ -2063,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",
@ -3270,9 +3290,10 @@
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz",
"integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.15",
"debug": "^4.3.6",

View File

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
"version": "2025.3.0",
"version": "2025.3.1",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
@ -20,12 +20,13 @@
"bootstrap-tokenfield": "^0.12.0",
"chainpad": "^5.2.6",
"chainpad-crypto": "^0.3.0",
"chainpad-listmap": "^1.1.1",
"chainpad-netflux": "^1.2.3",
"chainpad-listmap": "^1.2.0",
"chainpad-netflux": "^1.3.0",
"chainpad-server": "^5.2.4",
"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

@ -4,8 +4,10 @@
(() => {
const factory = () => {
const version = 8;
return {
currentVersion: 'v7'
currentVersionNumber: version,
currentVersion: 'v' + version
};
};

View File

@ -27,7 +27,7 @@ const factory = (AppConfig = {}, ApiConfig = {},
setCustomize({AppConfig,ApiConfig});
}
const Types = { setCustomize };
const Types = { OO_APPS, setCustomize };
Types.__defineGetter__("availableTypes", function () {
if (ApiConfig.appsToDisable) {

View File

@ -3,18 +3,21 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
const factory = (Sortify, UserObject, ProxyManager,
Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Rpc, Merge, Cache,
SF, AccountTS, DriveTS, Cursor,
Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
Calendar, Block, NetConfig, AppConfig = {},
Calendar, BadgeTS, Block, NetConfig,
Crypto, ChainPad, CpNetflux, Listmap,
Netflux, nThen) => {
let ApiConfig = {};
let AppConfig = {};
const Account = AccountTS.Account;
const Drive = DriveTS.Drive;
const Badge = BadgeTS.Badge;
const window = globalThis;
globalThis.nacl = globalThis.nacl || Crypto.Nacl;
@ -659,11 +662,15 @@ const factory = (ApiConfig = {}, 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,
@ -2807,6 +2814,7 @@ const factory = (ApiConfig = {}, 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'];
@ -2833,7 +2841,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
// Call onCacheReady if the manager is not yet defined
if (!manager) {
onCacheReady(clientId, waitFor());
startCacheModules(clientId, returned, waitFor());
manager = store.manager;
userObject = store.userObject;
}
@ -3193,10 +3201,11 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
andThen();
};
const startCacheModules = (clientId, returned, cb) => {
const startCacheModules = (clientId, returned, _cb) => {
const cb = Util.mkAsync(_cb);
onCacheReady(clientId, function () {
cb(returned);
onCacheReadyEvt.fire();
cb(returned);
});
};
const startModules = (clientId, returned, cb) => {
@ -3231,6 +3240,10 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
const requires = data.requires;
// data.noDrive indicates that we can use drive-less
// mode. It will be false is we are loading a safe hash
const requiresDrive = !data.noDrive;
// Mark initialized to true: new tabs will have to
// wait for the entire worker to be ready
initialized = true;
@ -3247,7 +3260,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
// Now users will create a drive, but they may not
// own an account
if (requires === 'pad') {
if (requires === 'pad' && !requiresDrive) {
// Start with only the pad modules, callback
// and then load the account and other modules
return void onNoDrive(clientId, function (obj) {
@ -3270,7 +3283,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
onPadRejectedEvt.reg(next);
});
}
if (requires === 'file') {
if (requires === 'file' && !requiresDrive) {
// Start with only the pad modules, callback
// and then load the account and other modules
return void onNoDrive(clientId, function (obj) {
@ -3455,7 +3468,6 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
undefined,
require('json.sortify'),
require('../common/user-object'),
require('../common/proxy-manager'),
@ -3465,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'),
@ -3483,9 +3495,9 @@ 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'),
undefined,
require('chainpad-crypto'),
require('chainpad'),
require('chainpad-netflux'),

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',
@ -20,13 +19,14 @@ define([
'/common/common-credential.js',
'/customize/login.js',
'/common/store-interface.js',
'/common/pad-types.js',
'/customize/application_config.js',
'/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,
Cred, Login, Store, AppConfig, nThen) {
Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Cred, Login, Store, Types, AppConfig, nThen) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@ -1606,7 +1606,7 @@ define([
oldChannel = oldSecret.channel;
var src = fileHost + Hash.getBlobPathFromHex(oldChannel);
var key = oldSecret.keys && oldSecret.keys.cryptKey;
var cryptKey = window.Util.encodeBase64(key);
var cryptKey = Util.encodeBase64(key);
var mt = document.createElement('media-tag');
mt.setAttribute('src', src);
@ -1690,7 +1690,7 @@ define([
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
if (parsed.type !== 'sheet') { return void cb({ error: 'EINVAL_TYPE' }); }
if (!Types.OO_APPS.includes(parsed.type)) { return void cb({ error: 'EINVAL_TYPE' }); }
var warning = false;
var newHash, newRoHref;
@ -2468,9 +2468,9 @@ define([
};
common.getAnonymousKeys = function (formSeed, channel) {
var array = window.Util.decodeBase64(formSeed + channel);
var array = Util.decodeBase64(formSeed + channel);
var hash = window.nacl.hash(array);
var secretKey = window.Util.encodeBase64(hash.subarray(32));
var secretKey = Util.encodeBase64(hash.subarray(32));
var publicKey = Hash.getCurvePublicFromPrivate(secretKey);
return {
curvePrivate: secretKey,

View File

@ -8,11 +8,12 @@ define([
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/pad-types.js',
'/common/inner/common-modal.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, Modal, h,
], function ($, Util, Hash, UI, UIElements, Types, Modal, h,
Messages, nThen) {
var Access = {};
@ -908,7 +909,7 @@ define([
// We should know it because the pad is stored, but it's better to check...
//if (!data.noEditPassword && !opts.noEditPassword && owned && data.href) {
if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption)
var isOO = parsed.type === 'sheet';
var isOO = Types?.OO_APPS?.includes(parsed.type);
var isFile = parsed.hashData.type === 'file';
var isSharedFolder = parsed.type === 'drive';

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

@ -4,8 +4,10 @@
(() => {
const factory = () => {
const version = 8;
return {
currentVersion: 'v7'
currentVersionNumber: version,
currentVersion: 'v' + version
};
};

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ const factory = (AppConfig = {}, ApiConfig = {},
setCustomize({AppConfig,ApiConfig});
}
const Types = { setCustomize };
const Types = { OO_APPS, setCustomize };
Types.__defineGetter__("availableTypes", function () {
if (ApiConfig.appsToDisable) {

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

@ -5,7 +5,8 @@
(() => {
const factory = function () {
let USE_MIN = true;
if (localStorage.CryptPad_dev === "1") { USE_MIN = false; }
if (typeof(localStorage) !== "undefined" &&
localStorage.CryptPad_dev === "1") { USE_MIN = false; }
let path = '/common/worker.bundle.js?';
if (USE_MIN) { path = '/common/worker.bundle.min.js?'; }

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 (false && data.badge && data.edPublic) { // XXX 2025.6
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); // XXX 2025.6
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', {