mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-25 09:50:35 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab4304e04 |
8
.github/workflows/docker.yml
vendored
8
.github/workflows/docker.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
platform=${{ matrix.platforms }}
|
platform=${{ matrix.platforms }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
id: cache-node-modules
|
id: cache-node-modules
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: var-cache-node-modules
|
path: var-cache-node-modules
|
||||||
key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }}
|
key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }}
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: digests-${{ env.PLATFORM_PAIR }}
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
path: ${{ runner.temp }}/digests/*
|
path: ${{ runner.temp }}/digests/*
|
||||||
@@ -93,7 +93,7 @@ jobs:
|
|||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
|
||||||
echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
|
echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ${{ runner.temp }}/digests
|
path: ${{ runner.temp }}/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -81,7 +81,7 @@ jobs:
|
|||||||
- 27017:27017
|
- 27017:27017
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- run: cp install/package.json package.json
|
- run: cp install/package.json package.json
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "nodebb",
|
"name": "nodebb",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"description": "NodeBB Forum",
|
"description": "NodeBB Forum",
|
||||||
"version": "4.7.1",
|
"version": "4.7.2",
|
||||||
"homepage": "https://www.nodebb.org",
|
"homepage": "https://www.nodebb.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -33,16 +33,16 @@
|
|||||||
"@fontsource/inter": "5.2.8",
|
"@fontsource/inter": "5.2.8",
|
||||||
"@fontsource/poppins": "5.2.7",
|
"@fontsource/poppins": "5.2.7",
|
||||||
"@fortawesome/fontawesome-free": "6.7.2",
|
"@fortawesome/fontawesome-free": "6.7.2",
|
||||||
"@isaacs/ttlcache": "2.1.3",
|
"@isaacs/ttlcache": "2.1.2",
|
||||||
"@nodebb/spider-detector": "2.0.3",
|
"@nodebb/spider-detector": "2.0.3",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@textcomplete/contenteditable": "0.1.13",
|
"@textcomplete/contenteditable": "0.1.13",
|
||||||
"@textcomplete/core": "0.1.13",
|
"@textcomplete/core": "0.1.13",
|
||||||
"@textcomplete/textarea": "0.1.13",
|
"@textcomplete/textarea": "0.1.13",
|
||||||
"ace-builds": "1.43.5",
|
"ace-builds": "1.43.4",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"async": "3.2.6",
|
"async": "3.2.6",
|
||||||
"autoprefixer": "10.4.23",
|
"autoprefixer": "10.4.22",
|
||||||
"bcryptjs": "3.0.3",
|
"bcryptjs": "3.0.3",
|
||||||
"benchpressjs": "2.5.5",
|
"benchpressjs": "2.5.5",
|
||||||
"body-parser": "2.2.1",
|
"body-parser": "2.2.1",
|
||||||
@@ -57,27 +57,27 @@
|
|||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"compression": "1.8.1",
|
"compression": "1.8.1",
|
||||||
"connect-flash": "0.1.1",
|
"connect-flash": "0.1.1",
|
||||||
"connect-mongo": "6.0.0",
|
"connect-mongo": "5.1.0",
|
||||||
"connect-pg-simple": "10.0.0",
|
"connect-pg-simple": "10.0.0",
|
||||||
"connect-redis": "9.0.0",
|
"connect-redis": "9.0.0",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"cron": "4.4.0",
|
"cron": "4.3.4",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"csrf-sync": "4.2.1",
|
"csrf-sync": "4.2.1",
|
||||||
"daemon": "1.1.0",
|
"daemon": "1.1.0",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.0",
|
||||||
"express": "4.22.1",
|
"express": "4.21.2",
|
||||||
"express-session": "1.18.2",
|
"express-session": "1.18.2",
|
||||||
"express-useragent": "2.0.2",
|
"express-useragent": "2.0.2",
|
||||||
"fetch-cookie": "3.2.0",
|
"fetch-cookie": "3.1.0",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"fs-extra": "11.3.3",
|
"fs-extra": "11.3.2",
|
||||||
"graceful-fs": "4.2.11",
|
"graceful-fs": "4.2.11",
|
||||||
"helmet": "7.2.0",
|
"helmet": "7.2.0",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"imagesloaded": "5.0.0",
|
"imagesloaded": "5.0.0",
|
||||||
"ipaddr.js": "2.3.0",
|
"ipaddr.js": "2.2.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-deserialize": "2.0.0",
|
"jquery-deserialize": "2.0.0",
|
||||||
"jquery-form": "4.3.0",
|
"jquery-form": "4.3.0",
|
||||||
@@ -85,13 +85,13 @@
|
|||||||
"jquery-ui": "1.14.1",
|
"jquery-ui": "1.14.1",
|
||||||
"jsesc": "3.1.0",
|
"jsesc": "3.1.0",
|
||||||
"json2csv": "5.0.7",
|
"json2csv": "5.0.7",
|
||||||
"jsonwebtoken": "9.0.3",
|
"jsonwebtoken": "9.0.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"logrotate-stream": "0.2.9",
|
"logrotate-stream": "0.2.9",
|
||||||
"lru-cache": "11.2.4",
|
"lru-cache": "11.2.2",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"mkdirp": "3.0.1",
|
"mkdirp": "3.0.1",
|
||||||
"mongodb": "7.0.0",
|
"mongodb": "6.21.0",
|
||||||
"morgan": "1.10.1",
|
"morgan": "1.10.1",
|
||||||
"mousetrap": "1.6.5",
|
"mousetrap": "1.6.5",
|
||||||
"multer": "2.0.2",
|
"multer": "2.0.2",
|
||||||
@@ -107,12 +107,12 @@
|
|||||||
"nodebb-plugin-spam-be-gone": "2.3.2",
|
"nodebb-plugin-spam-be-gone": "2.3.2",
|
||||||
"nodebb-plugin-web-push": "0.7.6",
|
"nodebb-plugin-web-push": "0.7.6",
|
||||||
"nodebb-rewards-essentials": "1.0.2",
|
"nodebb-rewards-essentials": "1.0.2",
|
||||||
"nodebb-theme-harmony": "2.1.28",
|
"nodebb-theme-harmony": "2.1.26",
|
||||||
"nodebb-theme-lavender": "7.1.19",
|
"nodebb-theme-lavender": "7.1.19",
|
||||||
"nodebb-theme-peace": "2.2.49",
|
"nodebb-theme-peace": "2.2.49",
|
||||||
"nodebb-theme-persona": "14.1.20",
|
"nodebb-theme-persona": "14.1.18",
|
||||||
"nodebb-widget-essentials": "7.0.41",
|
"nodebb-widget-essentials": "7.0.41",
|
||||||
"nodemailer": "7.0.12",
|
"nodemailer": "7.0.10",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"passport": "0.7.0",
|
"passport": "0.7.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
@@ -124,24 +124,24 @@
|
|||||||
"pretty": "^2.0.0",
|
"pretty": "^2.0.0",
|
||||||
"progress-webpack-plugin": "1.0.16",
|
"progress-webpack-plugin": "1.0.16",
|
||||||
"prompt": "1.3.0",
|
"prompt": "1.3.0",
|
||||||
"redis": "5.10.0",
|
"redis": "5.9.0",
|
||||||
"rimraf": "6.1.2",
|
"rimraf": "6.1.2",
|
||||||
"rss": "1.2.2",
|
"rss": "1.2.2",
|
||||||
"rtlcss": "4.3.0",
|
"rtlcss": "4.3.0",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"sass": "1.97.1",
|
"sass": "1.94.1",
|
||||||
"satori": "0.18.3",
|
"satori": "0.18.3",
|
||||||
"sbd": "^1.0.19",
|
"sbd": "^1.0.19",
|
||||||
"semver": "7.7.3",
|
"semver": "7.7.3",
|
||||||
"serve-favicon": "2.5.1",
|
"serve-favicon": "2.5.1",
|
||||||
"sharp": "0.34.5",
|
"sharp": "0.34.5",
|
||||||
"sitemap": "9.0.0",
|
"sitemap": "9.0.0",
|
||||||
"socket.io": "4.8.3",
|
"socket.io": "4.8.1",
|
||||||
"socket.io-client": "4.8.3",
|
"socket.io-client": "4.8.1",
|
||||||
"@socket.io/redis-adapter": "8.3.0",
|
"@socket.io/redis-adapter": "8.3.0",
|
||||||
"sortablejs": "1.15.6",
|
"sortablejs": "1.15.6",
|
||||||
"spdx-license-list": "6.10.0",
|
"spdx-license-list": "6.10.0",
|
||||||
"terser-webpack-plugin": "5.3.16",
|
"terser-webpack-plugin": "5.3.14",
|
||||||
"textcomplete": "0.18.2",
|
"textcomplete": "0.18.2",
|
||||||
"textcomplete.contenteditable": "0.1.1",
|
"textcomplete.contenteditable": "0.1.1",
|
||||||
"timeago": "1.6.7",
|
"timeago": "1.6.7",
|
||||||
@@ -149,10 +149,10 @@
|
|||||||
"toobusy-js": "0.5.1",
|
"toobusy-js": "0.5.1",
|
||||||
"tough-cookie": "6.0.0",
|
"tough-cookie": "6.0.0",
|
||||||
"undici": "^7.10.0",
|
"undici": "^7.10.0",
|
||||||
"validator": "13.15.26",
|
"validator": "13.15.23",
|
||||||
"webpack": "5.104.1",
|
"webpack": "5.103.0",
|
||||||
"webpack-merge": "6.0.1",
|
"webpack-merge": "6.0.1",
|
||||||
"winston": "3.19.0",
|
"winston": "3.18.3",
|
||||||
"workerpool": "10.0.1",
|
"workerpool": "10.0.1",
|
||||||
"xml": "1.0.1",
|
"xml": "1.0.1",
|
||||||
"xregexp": "5.1.2",
|
"xregexp": "5.1.2",
|
||||||
@@ -161,26 +161,26 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.1.0",
|
"@apidevtools/swagger-parser": "10.1.0",
|
||||||
"@commitlint/cli": "20.2.0",
|
"@commitlint/cli": "20.1.0",
|
||||||
"@commitlint/config-angular": "20.2.0",
|
"@commitlint/config-angular": "20.0.0",
|
||||||
"coveralls": "3.1.1",
|
"coveralls": "3.1.1",
|
||||||
"@eslint/js": "9.39.2",
|
"@eslint/js": "9.39.1",
|
||||||
"@stylistic/eslint-plugin": "5.6.1",
|
"@stylistic/eslint-plugin": "5.6.1",
|
||||||
"eslint-config-nodebb": "1.1.11",
|
"eslint-config-nodebb": "1.1.11",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"grunt": "1.6.1",
|
"grunt": "1.6.1",
|
||||||
"grunt-contrib-watch": "1.1.0",
|
"grunt-contrib-watch": "1.1.0",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"jsdom": "27.3.0",
|
"jsdom": "27.2.0",
|
||||||
"lint-staged": "16.2.7",
|
"lint-staged": "16.2.6",
|
||||||
"mocha": "11.7.5",
|
"mocha": "11.7.5",
|
||||||
"mocha-lcov-reporter": "1.3.0",
|
"mocha-lcov-reporter": "1.3.0",
|
||||||
"mockdate": "3.0.5",
|
"mockdate": "3.0.5",
|
||||||
"nyc": "17.1.0",
|
"nyc": "17.1.0",
|
||||||
"smtp-server": "3.17.1"
|
"smtp-server": "3.16.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"sass-embedded": "1.97.1"
|
"sass-embedded": "1.93.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/jquery": "3.7.1"
|
"*/jquery": "3.7.1"
|
||||||
@@ -203,4 +203,4 @@
|
|||||||
"url": "https://github.com/barisusakli"
|
"url": "https://github.com/barisusakli"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ define('quickreply', [
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replyMsg = element.val();
|
const replyMsg = components.get('topic/quickreply/text').val();
|
||||||
const replyData = {
|
const replyData = {
|
||||||
tid: ajaxify.data.tid,
|
tid: ajaxify.data.tid,
|
||||||
handle: undefined,
|
handle: undefined,
|
||||||
@@ -74,11 +74,9 @@ define('quickreply', [
|
|||||||
}
|
}
|
||||||
|
|
||||||
ready = false;
|
ready = false;
|
||||||
element.val('');
|
|
||||||
api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) {
|
api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) {
|
||||||
ready = true;
|
ready = true;
|
||||||
if (err) {
|
if (err) {
|
||||||
element.val(replyMsg);
|
|
||||||
return alerts.error(err);
|
return alerts.error(err);
|
||||||
}
|
}
|
||||||
if (data && data.queued) {
|
if (data && data.queued) {
|
||||||
@@ -93,7 +91,7 @@ define('quickreply', [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
element.val('');
|
components.get('topic/quickreply/text').val('');
|
||||||
storage.removeItem(qrDraftId);
|
storage.removeItem(qrDraftId);
|
||||||
QuickReply._autocomplete.hide();
|
QuickReply._autocomplete.hide();
|
||||||
hooks.fire('action:quickreply.success', { data });
|
hooks.fire('action:quickreply.success', { data });
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ Actors.assert = async (ids, options = {}) => {
|
|||||||
categories.add(actor.id);
|
categories.add(actor.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!typeOk ||
|
!typeOk ||
|
||||||
!activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop))
|
!activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop))
|
||||||
@@ -351,7 +351,7 @@ Actors.assertGroup = async (ids, options = {}) => {
|
|||||||
}));
|
}));
|
||||||
groups = groups.filter(Boolean); // remove unresolvable actors
|
groups = groups.filter(Boolean); // remove unresolvable actors
|
||||||
|
|
||||||
// Build categoryData object for storage
|
// Build userData object for storage
|
||||||
const categoryObjs = (await activitypub.mocks.category(groups)).filter(Boolean);
|
const categoryObjs = (await activitypub.mocks.category(groups)).filter(Boolean);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
@@ -400,20 +400,12 @@ Actors.assertGroup = async (ids, options = {}) => {
|
|||||||
db.deleteObjectFields('handle:cid', queries.handleRemove),
|
db.deleteObjectFields('handle:cid', queries.handleRemove),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Privilege mask
|
|
||||||
const [masksAdd, masksRemove] = categoryObjs.reduce(([add, remove], category) => {
|
|
||||||
(category?._activitypub?.postingRestrictedToMods ? add : remove).push(`cid:${category.cid}:privilegeMask`);
|
|
||||||
return [add, remove];
|
|
||||||
}, [[], []]);
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
db.setObjectBulk(bulkSet),
|
db.setObjectBulk(bulkSet),
|
||||||
db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)),
|
db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)),
|
||||||
db.sortedSetAddBulk(queries.searchAdd),
|
db.sortedSetAddBulk(queries.searchAdd),
|
||||||
db.setObject('handle:cid', queries.handleAdd),
|
db.setObject('handle:cid', queries.handleAdd),
|
||||||
_migratePersonToGroup(categoryObjs),
|
_migratePersonToGroup(categoryObjs),
|
||||||
db.setsAdd(masksAdd, 'topics:create'),
|
|
||||||
db.setsRemove(masksRemove, 'topics:create'),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return categoryObjs;
|
return categoryObjs;
|
||||||
|
|||||||
@@ -352,26 +352,6 @@ inbox.like = async (req) => {
|
|||||||
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
|
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
|
||||||
};
|
};
|
||||||
|
|
||||||
inbox.dislike = async (req) => {
|
|
||||||
const { actor, object } = req.body;
|
|
||||||
const { type, id } = await activitypub.helpers.resolveLocalId(object.id);
|
|
||||||
|
|
||||||
if (type !== 'post' || !(await posts.exists(id))) {
|
|
||||||
return reject('Dislike', object, actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowed = await privileges.posts.can('posts:downvote', id, activitypub._constants.uid);
|
|
||||||
if (!allowed) {
|
|
||||||
activitypub.helpers.log(`[activitypub/inbox.like] ${id} not allowed to be downvoted.`);
|
|
||||||
return reject('Dislike', object, actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
activitypub.helpers.log(`[activitypub/inbox/dislike] id ${id} via ${actor}`);
|
|
||||||
|
|
||||||
await posts.downvote(id, actor);
|
|
||||||
await activitypub.feps.announce(object.id, req.body);
|
|
||||||
};
|
|
||||||
|
|
||||||
inbox.announce = async (req) => {
|
inbox.announce = async (req) => {
|
||||||
let { actor, object, published, to, cc } = req.body;
|
let { actor, object, published, to, cc } = req.body;
|
||||||
activitypub.helpers.log(`[activitypub/inbox/announce] Parsing Announce(${object.type}) from ${actor}`);
|
activitypub.helpers.log(`[activitypub/inbox/announce] Parsing Announce(${object.type}) from ${actor}`);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const categories = require('../categories');
|
|||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
const topics = require('../topics');
|
const topics = require('../topics');
|
||||||
const messaging = require('../messaging');
|
const messaging = require('../messaging');
|
||||||
const privileges = require('../privileges');
|
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const slugify = require('../slugify');
|
const slugify = require('../slugify');
|
||||||
const translator = require('../translator');
|
const translator = require('../translator');
|
||||||
@@ -281,7 +280,6 @@ Mocks.category = async (actors) => {
|
|||||||
let {
|
let {
|
||||||
url, preferredUsername, icon, /* image, */
|
url, preferredUsername, icon, /* image, */
|
||||||
name, summary, followers, inbox, endpoints, tag,
|
name, summary, followers, inbox, endpoints, tag,
|
||||||
postingRestrictedToMods,
|
|
||||||
} = actor;
|
} = actor;
|
||||||
preferredUsername = slugify(preferredUsername || name);
|
preferredUsername = slugify(preferredUsername || name);
|
||||||
/*
|
/*
|
||||||
@@ -339,10 +337,6 @@ Mocks.category = async (actors) => {
|
|||||||
inbox,
|
inbox,
|
||||||
sharedInbox: endpoints ? endpoints.sharedInbox : null,
|
sharedInbox: endpoints ? endpoints.sharedInbox : null,
|
||||||
followersUrl: followers,
|
followersUrl: followers,
|
||||||
|
|
||||||
_activitypub: {
|
|
||||||
postingRestrictedToMods,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
@@ -530,19 +524,12 @@ Mocks.actors.user = async (uid) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Mocks.actors.category = async (cid) => {
|
Mocks.actors.category = async (cid) => {
|
||||||
const [
|
const {
|
||||||
{
|
name, handle: preferredUsername, slug,
|
||||||
name, handle: preferredUsername, slug,
|
descriptionParsed: summary, backgroundImage,
|
||||||
descriptionParsed: summary, backgroundImage,
|
} = await categories.getCategoryFields(cid,
|
||||||
},
|
['name', 'handle', 'slug', 'description', 'descriptionParsed', 'backgroundImage']);
|
||||||
publicKey,
|
const publicKey = await activitypub.getPublicKey('cid', cid);
|
||||||
canPost,
|
|
||||||
] = await Promise.all([
|
|
||||||
categories.getCategoryFields(cid,
|
|
||||||
['name', 'handle', 'slug', 'description', 'descriptionParsed', 'backgroundImage']),
|
|
||||||
activitypub.getPublicKey('cid', cid),
|
|
||||||
privileges.categories.can('topics:create', cid, -2),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (backgroundImage) {
|
if (backgroundImage) {
|
||||||
@@ -566,7 +553,6 @@ Mocks.actors.category = async (cid) => {
|
|||||||
'@context': [
|
'@context': [
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
'https://join-lemmy.org/context.json',
|
|
||||||
],
|
],
|
||||||
id: `${nconf.get('url')}/category/${cid}`,
|
id: `${nconf.get('url')}/category/${cid}`,
|
||||||
url: `${nconf.get('url')}/category/${slug}`,
|
url: `${nconf.get('url')}/category/${slug}`,
|
||||||
@@ -581,7 +567,6 @@ Mocks.actors.category = async (cid) => {
|
|||||||
summary,
|
summary,
|
||||||
// image, // todo once categories have cover photos
|
// image, // todo once categories have cover photos
|
||||||
icon,
|
icon,
|
||||||
postingRestrictedToMods: !canPost,
|
|
||||||
|
|
||||||
publicKey: {
|
publicKey: {
|
||||||
id: `${nconf.get('url')}/category/${cid}#key`,
|
id: `${nconf.get('url')}/category/${cid}#key`,
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
|||||||
|
|
||||||
await Notes.syncUserInboxes(tid, uid);
|
await Notes.syncUserInboxes(tid, uid);
|
||||||
|
|
||||||
if (!hasTid && uid && options.cid) {
|
if (!hasTid && options.cid) {
|
||||||
// New topic, have category announce it
|
// New topic, have category announce it
|
||||||
await activitypub.out.announce.topic(tid);
|
await activitypub.out.announce.topic(tid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,32 +277,6 @@ Out.like.note = enabledCheck(async (uid, pid) => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
Out.dislike = {};
|
|
||||||
|
|
||||||
Out.dislike.note = enabledCheck(async (uid, pid) => {
|
|
||||||
const payload = {
|
|
||||||
id: `${nconf.get('url')}/uid/${uid}#activity/dislike/${encodeURIComponent(pid)}`,
|
|
||||||
type: 'Dislike',
|
|
||||||
actor: `${nconf.get('url')}/uid/${uid}`,
|
|
||||||
object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes
|
|
||||||
await activitypub.feps.announce(pid, payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipient = await posts.getPostField(pid, 'uid');
|
|
||||||
if (!activitypub.helpers.isUri(recipient)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
activitypub.send('uid', uid, [recipient], payload),
|
|
||||||
activitypub.feps.announce(pid, payload),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
Out.announce = {};
|
Out.announce = {};
|
||||||
|
|
||||||
Out.announce.topic = enabledCheck(async (tid) => {
|
Out.announce.topic = enabledCheck(async (tid) => {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const posts = require('../posts');
|
|||||||
const privileges = require('../privileges');
|
const privileges = require('../privileges');
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const activitypub = require('../activitypub');
|
const activitypub = require('../activitypub');
|
||||||
const utils = require('../utils');
|
|
||||||
const socketHelpers = require('../socket.io/helpers');
|
const socketHelpers = require('../socket.io/helpers');
|
||||||
const websockets = require('../socket.io');
|
const websockets = require('../socket.io');
|
||||||
const events = require('../events');
|
const events = require('../events');
|
||||||
@@ -67,22 +66,11 @@ exports.doTopicAction = async function (action, event, caller, { tids }) {
|
|||||||
const uids = await user.getUidsFromSet('users:online', 0, -1);
|
const uids = await user.getUidsFromSet('users:online', 0, -1);
|
||||||
|
|
||||||
await Promise.all(tids.map(async (tid) => {
|
await Promise.all(tids.map(async (tid) => {
|
||||||
const { title, cid, mainPid } = await topics.getTopicFields(tid, ['title', 'cid', 'mainPid']);
|
const title = await topics.getTopicField(tid, 'title');
|
||||||
const data = await topics.tools[action](tid, caller.uid);
|
const data = await topics.tools[action](tid, caller.uid);
|
||||||
const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids);
|
const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids);
|
||||||
socketHelpers.emitToUids(event, data, notifyUids);
|
socketHelpers.emitToUids(event, data, notifyUids);
|
||||||
await logTopicAction(action, caller, tid, title);
|
await logTopicAction(action, caller, tid, title);
|
||||||
|
|
||||||
switch(action) {
|
|
||||||
case 'delete': // falls through
|
|
||||||
case 'purge': {
|
|
||||||
if (utils.isNumber(cid) && parseInt(cid, 10) > 0) {
|
|
||||||
activitypub.out.remove.context(caller.uid, tid); // 7888-style
|
|
||||||
activitypub.out.delete.note(caller.uid, mainPid); // 1b12-style
|
|
||||||
activitypub.out.undo.announce('cid', cid, tid); // microblogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -147,34 +135,14 @@ async function executeCommand(caller, command, eventName, notification, data) {
|
|||||||
websockets.in(`uid_${caller.uid}`).emit(`posts.${command}`, result);
|
websockets.in(`uid_${caller.uid}`).emit(`posts.${command}`, result);
|
||||||
websockets.in(data.room_id).emit(`event:${eventName}`, result);
|
websockets.in(data.room_id).emit(`event:${eventName}`, result);
|
||||||
}
|
}
|
||||||
|
if (result && command === 'upvote') {
|
||||||
if (result) {
|
socketHelpers.upvote(result, notification);
|
||||||
switch (command) {
|
await activitypub.out.like.note(caller.uid, data.pid);
|
||||||
case 'upvote': {
|
} else if (result && notification) {
|
||||||
socketHelpers.upvote(result, notification);
|
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification);
|
||||||
await activitypub.out.like.note(caller.uid, data.pid);
|
} else if (result && command === 'unvote') {
|
||||||
break;
|
socketHelpers.rescindUpvoteNotification(data.pid, caller.uid);
|
||||||
}
|
await activitypub.out.undo.like(caller.uid, data.pid);
|
||||||
|
|
||||||
case 'downvote': {
|
|
||||||
await activitypub.out.dislike.note(caller.uid, data.pid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'unvote': {
|
|
||||||
socketHelpers.rescindUpvoteNotification(data.pid, caller.uid);
|
|
||||||
await activitypub.out.undo.like(caller.uid, data.pid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
if (notification) {
|
|
||||||
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,12 +325,13 @@ topicsAPI.move = async (caller, { tid, cid }) => {
|
|||||||
|
|
||||||
if (utils.isNumber(cid) && parseInt(cid, 10) === -1) {
|
if (utils.isNumber(cid) && parseInt(cid, 10) === -1) {
|
||||||
activitypub.out.remove.context(caller.uid, tid); // 7888-style
|
activitypub.out.remove.context(caller.uid, tid); // 7888-style
|
||||||
activitypub.out.delete.note(caller.uid, topicData.mainPid); // 1b12-style
|
activitypub.out.delete.note(caller.uid, topicData.mainPid); // threadiverse
|
||||||
|
// tbd: activitypubApi.undo.announce? // microblogs
|
||||||
} else {
|
} else {
|
||||||
activitypub.out.move.context(caller.uid, tid);
|
activitypub.out.move.context(caller.uid, tid);
|
||||||
activitypub.out.announce.topic(tid);
|
activitypub.out.announce.topic(tid);
|
||||||
}
|
}
|
||||||
activitypub.out.undo.announce('cid', topicData.cid, tid); // microblogs
|
activitypub.out.undo.announce('cid', topicData.cid, tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
await events.log({
|
await events.log({
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ mongoModule.init = async function (opts) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mongoModule.createSessionStore = async function (options) {
|
mongoModule.createSessionStore = async function (options) {
|
||||||
const { MongoStore } = require('connect-mongo');
|
const MongoStore = require('connect-mongo');
|
||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
|
|
||||||
const store = MongoStore.create({
|
const store = MongoStore.create({
|
||||||
|
|||||||
@@ -68,31 +68,6 @@ module.exports = function (module) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.setAddBulk = async function (data) {
|
|
||||||
if (!data.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
|
|
||||||
|
|
||||||
data.forEach(([key, member]) => {
|
|
||||||
bulk.find({ _key: key }).upsert().updateOne({
|
|
||||||
$addToSet: {
|
|
||||||
members: helpers.valueToString(member),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await bulk.execute();
|
|
||||||
} catch (err) {
|
|
||||||
if (err && err.message.includes('E11000 duplicate key error')) {
|
|
||||||
console.log(new Error('e11000').stack, data);
|
|
||||||
return await module.setAddBulk(data);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.setRemove = async function (key, value) {
|
module.setRemove = async function (key, value) {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
value = [value];
|
value = [value];
|
||||||
@@ -100,17 +75,11 @@ module.exports = function (module) {
|
|||||||
|
|
||||||
value = value.map(v => helpers.valueToString(v));
|
value = value.map(v => helpers.valueToString(v));
|
||||||
|
|
||||||
const coll = module.client.collection('objects');
|
await module.client.collection('objects').updateMany({
|
||||||
await coll.updateMany({
|
|
||||||
_key: Array.isArray(key) ? { $in: key } : key,
|
_key: Array.isArray(key) ? { $in: key } : key,
|
||||||
}, {
|
}, {
|
||||||
$pullAll: { members: value },
|
$pullAll: { members: value },
|
||||||
});
|
});
|
||||||
|
|
||||||
await coll.deleteMany({
|
|
||||||
_key: Array.isArray(key) ? { $in: key } : key,
|
|
||||||
members: { $size: 0 },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.setsRemove = async function (keys, value) {
|
module.setsRemove = async function (keys, value) {
|
||||||
@@ -119,17 +88,11 @@ module.exports = function (module) {
|
|||||||
}
|
}
|
||||||
value = helpers.valueToString(value);
|
value = helpers.valueToString(value);
|
||||||
|
|
||||||
const coll = module.client.collection('objects');
|
await module.client.collection('objects').updateMany({
|
||||||
await coll.updateMany({
|
|
||||||
_key: { $in: keys },
|
_key: { $in: keys },
|
||||||
}, {
|
}, {
|
||||||
$pull: { members: value },
|
$pull: { members: value },
|
||||||
});
|
});
|
||||||
|
|
||||||
await coll.deleteMany({
|
|
||||||
_key: { $in: keys },
|
|
||||||
members: { $size: 0 },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.isSetMember = async function (key, value) {
|
module.isSetMember = async function (key, value) {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ module.exports = function (module) {
|
|||||||
return members.map(member => member.length > 0);
|
return members.map(member => member.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkIfSetsExist(keys) {
|
|
||||||
const members = await Promise.all(keys.map(module.getSetMembers));
|
|
||||||
return members.map(member => member.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkIfKeysExist(keys) {
|
async function checkIfKeysExist(keys) {
|
||||||
const res = await module.pool.query({
|
const res = await module.pool.query({
|
||||||
name: 'existsArray',
|
name: 'existsArray',
|
||||||
@@ -49,16 +44,13 @@ module.exports = function (module) {
|
|||||||
if (isArray) {
|
if (isArray) {
|
||||||
const types = await Promise.all(key.map(module.type));
|
const types = await Promise.all(key.map(module.type));
|
||||||
const zsetKeys = key.filter((_key, i) => types[i] === 'zset');
|
const zsetKeys = key.filter((_key, i) => types[i] === 'zset');
|
||||||
const setKeys = key.filter((_key, i) => types[i] === 'set');
|
const otherKeys = key.filter((_key, i) => types[i] !== 'zset');
|
||||||
const otherKeys = key.filter((_key, i) => types[i] !== 'zset' && types[i] !== 'set');
|
const [zsetExits, otherExists] = await Promise.all([
|
||||||
const [zsetExits, setExists, otherExists] = await Promise.all([
|
|
||||||
checkIfzSetsExist(zsetKeys),
|
checkIfzSetsExist(zsetKeys),
|
||||||
checkIfSetsExist(setKeys),
|
|
||||||
checkIfKeysExist(otherKeys),
|
checkIfKeysExist(otherKeys),
|
||||||
]);
|
]);
|
||||||
const existsMap = Object.create(null);
|
const existsMap = Object.create(null);
|
||||||
zsetKeys.forEach((k, i) => { existsMap[k] = zsetExits[i]; });
|
zsetKeys.forEach((k, i) => { existsMap[k] = zsetExits[i]; });
|
||||||
setKeys.forEach((k, i) => { existsMap[k] = setExists[i]; });
|
|
||||||
otherKeys.forEach((k, i) => { existsMap[k] = otherExists[i]; });
|
otherKeys.forEach((k, i) => { existsMap[k] = otherExists[i]; });
|
||||||
return key.map(k => existsMap[k]);
|
return key.map(k => existsMap[k]);
|
||||||
}
|
}
|
||||||
@@ -66,9 +58,6 @@ module.exports = function (module) {
|
|||||||
if (type === 'zset') {
|
if (type === 'zset') {
|
||||||
const members = await module.getSortedSetRange(key, 0, 0);
|
const members = await module.getSortedSetRange(key, 0, 0);
|
||||||
return members.length > 0;
|
return members.length > 0;
|
||||||
} else if (type === 'set') {
|
|
||||||
const members = await module.getSetMembers(key);
|
|
||||||
return members.length > 0;
|
|
||||||
}
|
}
|
||||||
const res = await module.pool.query({
|
const res = await module.pool.query({
|
||||||
name: 'exists',
|
name: 'exists',
|
||||||
|
|||||||
@@ -54,32 +54,6 @@ DO NOTHING`,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.setAddBulk = async function (data) {
|
|
||||||
if (!data.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const keys = [];
|
|
||||||
const members = [];
|
|
||||||
|
|
||||||
for (const [key, member] of data) {
|
|
||||||
keys.push(key);
|
|
||||||
members.push(member);
|
|
||||||
}
|
|
||||||
await module.transaction(async (client) => {
|
|
||||||
await helpers.ensureLegacyObjectsType(client, keys, 'set');
|
|
||||||
await client.query({
|
|
||||||
name: 'setAddBulk',
|
|
||||||
text: `
|
|
||||||
INSERT INTO "legacy_set" ("_key", "member")
|
|
||||||
SELECT k, m
|
|
||||||
FROM UNNEST($1::TEXT[], $2::TEXT[]) AS t(k, m)
|
|
||||||
ON CONFLICT ("_key", "member")
|
|
||||||
DO NOTHING;`,
|
|
||||||
values: [keys, members],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.setRemove = async function (key, value) {
|
module.setRemove = async function (key, value) {
|
||||||
if (!Array.isArray(key)) {
|
if (!Array.isArray(key)) {
|
||||||
key = [key];
|
key = [key];
|
||||||
|
|||||||
@@ -14,29 +14,11 @@ module.exports = function (module) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.setsAdd = async function (keys, value) {
|
module.setsAdd = async function (keys, value) {
|
||||||
if (!Array.isArray(keys) || !keys.length || !value) {
|
if (!Array.isArray(keys) || !keys.length) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
value = [value];
|
|
||||||
}
|
|
||||||
if (!value.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const batch = module.client.batch();
|
const batch = module.client.batch();
|
||||||
keys.forEach((k) => {
|
keys.forEach(k => batch.sAdd(String(k), String(value)));
|
||||||
value.forEach(v => batch.sAdd(String(k), String(v)));
|
|
||||||
});
|
|
||||||
await helpers.execBatch(batch);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.setAddBulk = async function (data) {
|
|
||||||
if (!data.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const batch = module.client.batch();
|
|
||||||
data.forEach(([key, member]) => batch.sAdd(String(key), String(member)));
|
|
||||||
await helpers.execBatch(batch);
|
await helpers.execBatch(batch);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -96,16 +96,14 @@ privsCategories.get = async function (cid, uid) {
|
|||||||
'topics:tag', 'read', 'posts:view_deleted',
|
'topics:tag', 'read', 'posts:view_deleted',
|
||||||
];
|
];
|
||||||
|
|
||||||
let [userPrivileges, isAdministrator, isModerator] = await Promise.all([
|
const [userPrivileges, isAdministrator, isModerator] = await Promise.all([
|
||||||
helpers.isAllowedTo(privs, uid, cid),
|
helpers.isAllowedTo(privs, uid, cid),
|
||||||
user.isAdministrator(uid),
|
user.isAdministrator(uid),
|
||||||
user.isModerator(uid, cid),
|
user.isModerator(uid, cid),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (utils.isNumber(cid)) {
|
const combined = userPrivileges.map(allowed => allowed || isAdministrator);
|
||||||
userPrivileges = userPrivileges.map(allowed => allowed || isAdministrator);
|
const privData = _.zipObject(privs, combined);
|
||||||
}
|
|
||||||
const privData = _.zipObject(privs, userPrivileges);
|
|
||||||
const isAdminOrMod = isAdministrator || isModerator;
|
const isAdminOrMod = isAdministrator || isModerator;
|
||||||
|
|
||||||
return await plugins.hooks.fire('filter:privileges.categories.get', {
|
return await plugins.hooks.fire('filter:privileges.categories.get', {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
|
|
||||||
const db = require('../database');
|
|
||||||
const groups = require('../groups');
|
const groups = require('../groups');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const categories = require('../categories');
|
const categories = require('../categories');
|
||||||
@@ -26,25 +25,16 @@ helpers.isUsersAllowedTo = async function (privilege, uids, cid) {
|
|||||||
cid = -1;
|
cid = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowed;
|
const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([
|
||||||
const masked = await db.isSetMember(`cid:${cid}:privilegeMask`, privilege);
|
groups.isMembers(uids, `cid:${cid}:privileges:${privilege}`),
|
||||||
if (!masked) {
|
groups.isMembersOfGroupList(uids, `cid:${cid}:privileges:groups:${privilege}`),
|
||||||
const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([
|
]);
|
||||||
groups.isMembers(uids, `cid:${cid}:privileges:${privilege}`),
|
const allowed = uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]);
|
||||||
groups.isMembersOfGroupList(uids, `cid:${cid}:privileges:groups:${privilege}`),
|
const result = await plugins.hooks.fire('filter:privileges:isUsersAllowedTo', { allowed: allowed, privilege: privilege, uids: uids, cid: cid });
|
||||||
]);
|
|
||||||
allowed = uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]);
|
|
||||||
} else {
|
|
||||||
allowed = uids.map(() => false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await plugins.hooks.fire('filter:privileges:isUsersAllowedTo', { allowed, privilege, uids, cid });
|
|
||||||
return result.allowed;
|
return result.allowed;
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) {
|
helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) {
|
||||||
const _cid = cid; // original passed-in cid needed for privilege mask checks
|
|
||||||
|
|
||||||
// Remote categories (non-numeric) inherit world privileges
|
// Remote categories (non-numeric) inherit world privileges
|
||||||
if (Array.isArray(cid)) {
|
if (Array.isArray(cid)) {
|
||||||
cid = cid.map(cid => (utils.isNumber(cid) ? cid : -1));
|
cid = cid.map(cid => (utils.isNumber(cid) ? cid : -1));
|
||||||
@@ -54,15 +44,10 @@ helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) {
|
|||||||
|
|
||||||
let allowed;
|
let allowed;
|
||||||
if (Array.isArray(privilege) && !Array.isArray(cid)) {
|
if (Array.isArray(privilege) && !Array.isArray(cid)) {
|
||||||
const mask = await db.isSetMembers(`cid:${_cid}:privilegeMask`, privilege);
|
|
||||||
allowed = await isAllowedToPrivileges(privilege, uidOrGroupName, cid);
|
allowed = await isAllowedToPrivileges(privilege, uidOrGroupName, cid);
|
||||||
allowed = allowed.map((allowed, idx) => mask[idx] ? false : allowed);
|
|
||||||
} else if (Array.isArray(cid) && !Array.isArray(privilege)) {
|
} else if (Array.isArray(cid) && !Array.isArray(privilege)) {
|
||||||
const mask = await db.isMemberOfSets(_cid.map(cid => `cid:${cid}:privilegeMask`), privilege);
|
|
||||||
allowed = await isAllowedToCids(privilege, uidOrGroupName, cid);
|
allowed = await isAllowedToCids(privilege, uidOrGroupName, cid);
|
||||||
allowed = allowed.map((allowed, idx) => mask[idx] ? false : allowed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
({ allowed } = await plugins.hooks.fire('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
|
({ allowed } = await plugins.hooks.fire('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
|
||||||
return allowed;
|
return allowed;
|
||||||
|
|||||||
@@ -203,241 +203,4 @@ describe('Privilege logic for remote users/content (ActivityPub)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('Privilege masking', () => {
|
|
||||||
before(async () => {
|
|
||||||
// Grant default fediverse privileges
|
|
||||||
await install.giveWorldPrivileges();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('control', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a standard mock group
|
|
||||||
({ id: cid } = helpers.mocks.group());
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly assert the remote category', async () => {
|
|
||||||
const assertion = await activitypub.actors.assertGroup([cid]);
|
|
||||||
const exists = await categories.exists(cid);
|
|
||||||
|
|
||||||
assert(assertion);
|
|
||||||
assert(exists);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not set up a privilege mask for that category', async () => {
|
|
||||||
const exists = await db.exists(`cid:${cid}:privilegeMask`);
|
|
||||||
assert(!exists);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass the privileges .can() check if requested', async () => {
|
|
||||||
const set = await privileges.categories.get(cid, uid);
|
|
||||||
const can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(can);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true in the privilege set when requested', async () => {
|
|
||||||
const set = await privileges.categories.get(cid, uid);
|
|
||||||
|
|
||||||
assert(set);
|
|
||||||
assert(set['topics:create']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postingRestrictedToMods (true on assert)', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a mock group with `postingRestrictedToMods` bit set
|
|
||||||
({ id: cid } = helpers.mocks.group({
|
|
||||||
postingRestrictedToMods: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly assert the remote category', async () => {
|
|
||||||
const assertion = await activitypub.actors.assertGroup([cid]);
|
|
||||||
const exists = await categories.exists(cid);
|
|
||||||
|
|
||||||
assert(assertion);
|
|
||||||
assert(exists);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set up a privilege mask for that category', async () => {
|
|
||||||
const exists = await db.exists(`cid:${cid}:privilegeMask`);
|
|
||||||
assert(exists);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a single mask', async () => {
|
|
||||||
const members = await db.getSetMembers(`cid:${cid}:privilegeMask`);
|
|
||||||
|
|
||||||
assert(members);
|
|
||||||
assert.strictEqual(members.length, 1);
|
|
||||||
assert.strictEqual(members[0], 'topics:create');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail the privileges .can() check if requested', async () => {
|
|
||||||
const can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(!can);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false in the privilege set when requested', async () => {
|
|
||||||
const set = await privileges.categories.get(cid, uid);
|
|
||||||
|
|
||||||
assert(set);
|
|
||||||
assert(!set['topics:create']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postingRestrictedToMods (true on assert and re-assertion)', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a mock group with `postingRestrictedToMods` bit set
|
|
||||||
({ id: cid } = helpers.mocks.group({
|
|
||||||
postingRestrictedToMods: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
|
|
||||||
// Assert group, then re-assert again
|
|
||||||
await activitypub.actors.assertGroup([cid]);
|
|
||||||
await activitypub.actors.assertGroup([cid], { update: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail the privileges .can() check if requested', async () => {
|
|
||||||
const can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(!can);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false in the privilege set when requested', async () => {
|
|
||||||
const set = await privileges.categories.get(cid, uid);
|
|
||||||
|
|
||||||
assert(set);
|
|
||||||
assert(!set['topics:create']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postingRestrictedToMods (true on assert, false on update)', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a mock group with `postingRestrictedToMods` bit set
|
|
||||||
({ id: cid } = helpers.mocks.group({
|
|
||||||
postingRestrictedToMods: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
|
|
||||||
await activitypub.actors.assertGroup([cid]);
|
|
||||||
|
|
||||||
// Group updated "remotely"
|
|
||||||
helpers.mocks.group({
|
|
||||||
id: cid,
|
|
||||||
postingRestrictedToMods: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the privilege mask if the bit is not present on group actor update', async () => {
|
|
||||||
let can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(!can, 'Initial state should be denied due to mask.');
|
|
||||||
|
|
||||||
// Group re-assertion
|
|
||||||
await activitypub.actors.assertGroup([cid], { update: true });
|
|
||||||
|
|
||||||
// Ensure mask is gone from db
|
|
||||||
const memberCount = await db.setCount(`cid:${cid}:privilegeMask`);
|
|
||||||
assert.strictEqual(memberCount, 0);
|
|
||||||
|
|
||||||
can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(can, 'Privilege should be restored after mask removal.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postingRestrictedToMods (true on assert, property missing on update)', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a mock group with `postingRestrictedToMods` bit set
|
|
||||||
({ id: cid } = helpers.mocks.group({
|
|
||||||
postingRestrictedToMods: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
|
|
||||||
await activitypub.actors.assertGroup([cid]);
|
|
||||||
|
|
||||||
// Group updated "remotely"
|
|
||||||
helpers.mocks.group({
|
|
||||||
id: cid,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the privilege mask if the bit is not present on group actor update', async () => {
|
|
||||||
let can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(!can, 'Initial state should be denied due to mask.');
|
|
||||||
|
|
||||||
// Group re-assertion
|
|
||||||
await activitypub.actors.assertGroup([cid], { update: true });
|
|
||||||
|
|
||||||
// Ensure mask is gone from db
|
|
||||||
const memberCount = await db.setCount(`cid:${cid}:privilegeMask`);
|
|
||||||
assert.strictEqual(memberCount, 0);
|
|
||||||
|
|
||||||
can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(can, 'Privilege should be restored after mask removal.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postingRestrictedToMods (false on assert, true on update)', () => {
|
|
||||||
let cid;
|
|
||||||
let uid;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
// Set up a mock group with `postingRestrictedToMods` bit set to false
|
|
||||||
({ id: cid } = helpers.mocks.group({
|
|
||||||
postingRestrictedToMods: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Unprivileged user for testing
|
|
||||||
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
|
|
||||||
|
|
||||||
await activitypub.actors.assertGroup([cid]);
|
|
||||||
|
|
||||||
|
|
||||||
// Update group "remotely", re-assert
|
|
||||||
helpers.mocks.group({
|
|
||||||
id: cid,
|
|
||||||
postingRestrictedToMods: true,
|
|
||||||
});
|
|
||||||
await activitypub.actors.assertGroup([cid], { update: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail the privileges .can() check if requested', async () => {
|
|
||||||
const can = await privileges.categories.can('topics:create', cid, uid);
|
|
||||||
assert(!can);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false in the privilege set when requested', async () => {
|
|
||||||
const set = await privileges.categories.get(cid, uid);
|
|
||||||
|
|
||||||
assert(set);
|
|
||||||
assert(!set['topics:create']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const db = require('./mocks/databasemock');
|
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const request = require('../src/request');
|
const request = require('../src/request');
|
||||||
|
const db = require('./mocks/databasemock');
|
||||||
const Categories = require('../src/categories');
|
const Categories = require('../src/categories');
|
||||||
const Topics = require('../src/topics');
|
const Topics = require('../src/topics');
|
||||||
const User = require('../src/user');
|
const User = require('../src/user');
|
||||||
|
|||||||
@@ -88,36 +88,6 @@ describe('Set methods', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the values to each set', async () => {
|
|
||||||
await db.setsAdd(['saddarray1', 'saddarray2', 'saddarray3'], ['v1', 'v2', 'v3']);
|
|
||||||
const data = await db.getSetsMembers(['saddarray1', 'saddarray2', 'saddarray3']);
|
|
||||||
data.forEach(members => members.sort());
|
|
||||||
assert.deepStrictEqual(data, [
|
|
||||||
['v1', 'v2', 'v3'],
|
|
||||||
['v1', 'v2', 'v3'],
|
|
||||||
['v1', 'v2', 'v3'],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setAddBulk()', () => {
|
|
||||||
it('should add multiple key-member pairs', async () => {
|
|
||||||
await db.setAddBulk([
|
|
||||||
['bulkSet1', 'value1'],
|
|
||||||
['bulkSet2', 'value2'],
|
|
||||||
]);
|
|
||||||
let data = await db.getSetMembers('bulkSet1');
|
|
||||||
assert.deepStrictEqual(data, ['value1']);
|
|
||||||
data = await db.getSetMembers('bulkSet2');
|
|
||||||
assert.deepStrictEqual(data, ['value2']);
|
|
||||||
await db.setAddBulk([
|
|
||||||
['bulkSet1', 'value1'],
|
|
||||||
['bulkSet1', 'value3'],
|
|
||||||
]);
|
|
||||||
data = await db.getSetMembers('bulkSet1');
|
|
||||||
assert.deepStrictEqual(data.sort(), ['value1', 'value3']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSetsMembers()', () => {
|
describe('getSetsMembers()', () => {
|
||||||
@@ -238,42 +208,57 @@ describe('Set methods', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('setRemove()', () => {
|
describe('setRemove()', () => {
|
||||||
it('should remove an element from set', async () => {
|
before((done) => {
|
||||||
await db.setAdd('testSet6', [1, 2]);
|
db.setAdd('testSet6', [1, 2], done);
|
||||||
await db.setRemove('testSet6', '2');
|
|
||||||
|
|
||||||
const isMember = await db.isSetMember('testSet6', '2');
|
|
||||||
assert.equal(isMember, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove multiple elements from set', async () => {
|
it('should remove a element from set', (done) => {
|
||||||
await db.setAdd('multiRemoveSet', [1, 2, 3, 4, 5]);
|
db.setRemove('testSet6', '2', function (err) {
|
||||||
await db.setRemove('multiRemoveSet', [1, 3, 5]);
|
assert.equal(err, null);
|
||||||
|
assert.equal(arguments.length, 1);
|
||||||
|
|
||||||
const members = await db.getSetMembers('multiRemoveSet');
|
db.isSetMember('testSet6', '2', (err, isMember) => {
|
||||||
assert(members.includes('2'));
|
assert.equal(err, null);
|
||||||
assert(members.includes('4'));
|
assert.equal(isMember, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove multiple values from multiple keys', async () => {
|
it('should remove multiple elements from set', (done) => {
|
||||||
await db.setAdd('multiSetTest1', ['one', 'two', 'three', 'four']);
|
db.setAdd('multiRemoveSet', [1, 2, 3, 4, 5], (err) => {
|
||||||
await db.setAdd('multiSetTest2', ['three', 'four', 'five', 'six']);
|
assert.ifError(err);
|
||||||
await db.setRemove(['multiSetTest1', 'multiSetTest2'], ['three', 'four', 'five', 'doesnt exist']);
|
db.setRemove('multiRemoveSet', [1, 3, 5], (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
const members = await db.getSetsMembers(['multiSetTest1', 'multiSetTest2']);
|
db.getSetMembers('multiRemoveSet', (err, members) => {
|
||||||
assert.equal(members[0].length, 2);
|
assert.ifError(err);
|
||||||
assert.equal(members[1].length, 1);
|
assert(members.includes('2'));
|
||||||
assert(members[0].includes('one'));
|
assert(members.includes('4'));
|
||||||
assert(members[0].includes('two'));
|
done();
|
||||||
assert(members[1].includes('six'));
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove set if all elements are removed', async () => {
|
it('should remove multiple values from multiple keys', (done) => {
|
||||||
await db.setAdd('toBeDeletedSet', ['a', 'b']);
|
db.setAdd('multiSetTest1', ['one', 'two', 'three', 'four'], (err) => {
|
||||||
await db.setRemove('toBeDeletedSet', ['a', 'b']);
|
assert.ifError(err);
|
||||||
|
db.setAdd('multiSetTest2', ['three', 'four', 'five', 'six'], (err) => {
|
||||||
const exists = await db.exists('toBeDeletedSet');
|
assert.ifError(err);
|
||||||
assert.equal(exists, false);
|
db.setRemove(['multiSetTest1', 'multiSetTest2'], ['three', 'four', 'five', 'doesnt exist'], (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
db.getSetsMembers(['multiSetTest1', 'multiSetTest2'], (err, members) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(members[0].length, 2);
|
||||||
|
assert.equal(members[1].length, 1);
|
||||||
|
assert(members[0].includes('one'));
|
||||||
|
assert(members[0].includes('two'));
|
||||||
|
assert(members[1].includes('six'));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user