Merge branch 'bootstrap5' of https://github.com/NodeBB/NodeBB into bootstrap5

This commit is contained in:
Barış Soner Uşaklı
2023-01-24 10:42:03 -05:00
11 changed files with 84 additions and 14 deletions

View File

@@ -77,7 +77,7 @@
"jquery-ui": "1.13.2",
"jsesc": "3.0.2",
"json2csv": "5.0.7",
"jsonwebtoken": "8.5.1",
"jsonwebtoken": "9.0.0",
"less": "4.1.3",
"lodash": "4.17.21",
"logrotate-stream": "0.2.8",
@@ -131,7 +131,7 @@
"slideout": "1.0.1",
"socket.io": "4.5.4",
"socket.io-client": "4.5.4",
"@socket.io/redis-adapter": "8.0.0",
"@socket.io/redis-adapter": "8.0.1",
"sortablejs": "1.15.0",
"spdx-license-list": "6.6.0",
"spider-detector": "2.0.0",
@@ -151,7 +151,7 @@
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@apidevtools/swagger-parser": "9.0.0",
"@commitlint/cli": "17.4.1",
"@commitlint/config-angular": "17.4.0",
"coveralls": "3.1.1",

View File

@@ -20,7 +20,7 @@
"headers.coep-help": "Khi được bật (mặc định), sẽ đặt tiêu đề thành <code>require-corp</code>",
"headers.coop": "Cross-Origin-Opener-Policy",
"headers.corp": "Cross-Origin-Resource-Policy",
"headers.permissions-policy": "Permissions-Policy",
"headers.permissions-policy": "Quyền-Chính sách",
"headers.permissions-policy-help": "Allows setting permissions policy header, for example \"geolocation=*, camera=()\", see <a href=\"https://github.com/w3c/webappsec-permissions-policy/blob/main/permissions-policy-explainer.md\">this</a> for more info.",
"hsts": "Bảo Vệ Truyền Tải Nghiêm Ngặt",
"hsts.enabled": "Đã bật HSTS (đề nghị)",

View File

@@ -6,7 +6,7 @@
"from-help": "Tên người gửi hiển thị trong email.",
"confirmation-settings": "Xác nhận",
"confirmation.expiry": "Hours to keep email confirmation link valid",
"confirmation.expiry": "Số giờ để giữ cho liên kết xác nhận email hợp lệ",
"smtp-transport": "Truyền Tải SMTP",
"smtp-transport.enabled": "Bật truyền tải SMTP",

View File

@@ -27,5 +27,5 @@
"flags.action-on-resolve": "Do the following when a flag is resolved",
"flags.action-on-reject": "Do the following when a flag is rejected",
"flags.action.nothing": "Do nothing",
"flags.action.rescind": "Rescind the notification send to moderators/administrators"
"flags.action.rescind": "Hủy bỏ gửi thông báo cho người điều hành/quản trị viên"
}

View File

@@ -1,7 +1,7 @@
{
"authentication": "Xác thực",
"email-confirm-interval": "Người dùng không thể gửi lại email xác nhận cho đến khi",
"email-confirm-interval2": "minutes have elapsed",
"email-confirm-interval2": "phút đã trôi qua",
"allow-login-with": "Cho phép đăng nhập với",
"allow-login-with.username-email": "Tên Đăng Nhập hoặc Email",
"allow-login-with.username": "Chỉ Tên Đăng Nhập",
@@ -29,8 +29,8 @@
"session-time-days": "Ngày",
"session-time-seconds": "Giây",
"session-time-help": "Giá trị này dùng để điều chỉnh thời gian người dùng đăng nhập khi họ chọn &quot;Nhớ Tôi&quot; lúc đăng nhập. Lưu ý chỉ một trong những giá trị này sẽ được dùng. Nếu không có giá trị <i>giây</i> chúng tôi sẽ dùng <i>ngày</i>. Nếu không có <i>ngày</i> mặc định là <i>14 ngày</i>.",
"session-duration": "Session length if \"Remember Me\" is not checked (seconds)",
"session-duration-help": "By default — or if set to <code>0</code> — a user will stay logged in for the duration of the session (e.g. however long the browser window/tab remains open). Set this value to explicitly invalidate the session after the specified number of seconds.",
"session-duration": "Thời lượng phiên nếu \"Ghi nhớ tôi\" không được chọn (giây)",
"session-duration-help": "Theo mặc định — hoặc nếu đặt thành <code>0</code> — người dùng sẽ duy trì trạng thái đăng nhập trong suốt thời gian của phiên (VD: cửa sổ/tab trình duyệt vẫn mở trong bao lâu). Đặt giá trị này để vô hiệu hóa rõ ràng phiên sau số giây đã chỉ định.",
"online-cutoff": "Số phút sau khi người dùng được coi là không hoạt động",
"online-cutoff-help": "Nếu người dùng không thao tác trong khoảng thời gian này, được coi là không hoạt động và không nhận được cập nhật theo thời gian thực.",
"registration": "Đăng Ký Người Dùng",

View File

@@ -16,6 +16,57 @@ get:
responses:
'200':
description: user emails successfully listed
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
emails:
type: array
items:
type: string
description: An email address
post:
tags:
- users
summary: add email to user
description: |
This operation adds an email to the user account, optionally bypassing the confirmation step if requested.
**Note**: The confirmation bypass can only be called by super administrators or users with the `admin:users` privilege.
parameters:
- in: path
name: uid
schema:
type: integer
required: true
description: uid of the account to add the email
example: 1
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
description: A single email address
example: test@example.org
skipConfirmation:
type: boolean
description: If truthy, will automatically confirm the user's email.
example: 1
required:
- email
responses:
'200':
description: email successfully added to user account
content:
application/json:
schema:

View File

@@ -307,18 +307,17 @@ async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
async function processDeletion({ uid, method, password, caller }) {
const isTargetAdmin = await user.isAdministrator(uid);
const isSelf = parseInt(uid, 10) === parseInt(caller.uid, 10);
const isAdmin = await user.isAdministrator(caller.uid);
const hasAdminPrivilege = await privileges.admin.can('admin:users', caller.uid);
if (isSelf && meta.config.allowAccountDelete !== 1) {
throw new Error('[[error:account-deletion-disabled]]');
} else if (!isSelf && !isAdmin) {
} else if (!isSelf && !hasAdminPrivilege) {
throw new Error('[[error:no-privileges]]');
} else if (isTargetAdmin) {
throw new Error('[[error:cant-delete-admin]');
}
// Privilege checks -- only deleteAccount is available for non-admins
const hasAdminPrivilege = await privileges.admin.can('admin:users', caller.uid);
if (!hasAdminPrivilege && ['delete', 'deleteContent'].includes(method)) {
throw new Error('[[error:no-privileges]]');
}

View File

@@ -253,6 +253,24 @@ Users.getInviteGroups = async function (req, res) {
return helpers.formatApiResponse(200, res, userInviteGroups.map(group => group.displayName));
};
Users.addEmail = async (req, res) => {
const canManageUsers = await privileges.admin.can('admin:users', req.uid);
const skipConfirmation = canManageUsers && req.body.skipConfirmation;
if (skipConfirmation) {
await user.setUserField(req.params.uid, 'email', req.body.email);
await user.email.confirmByUid(req.params.uid);
} else {
await api.users.update(req, {
uid: req.params.uid,
email: req.body.email,
});
}
const emails = await db.getSortedSetRangeByScore('email:uid', 0, 500, req.params.uid, req.params.uid);
helpers.formatApiResponse(200, res, { emails });
};
Users.listEmails = async (req, res) => {
const [isPrivileged, { showemail }] = await Promise.all([
user.isPrivileged(req.uid),

View File

@@ -66,6 +66,7 @@ privsAdmin.routeMap = {
uploadDefaultAvatar: 'admin:settings',
};
privsAdmin.routePrefixMap = {
'dashboard/': 'admin:dashboard',
'manage/categories/': 'admin:categories',
'manage/privileges/': 'admin:privileges',
'manage/groups/': 'admin:groups',

View File

@@ -48,6 +48,7 @@ function authenticatedRoutes() {
setupApiRoute(router, 'get', '/:uid/invites/groups', [...middlewares, middleware.assert.user], controllers.write.users.getInviteGroups);
setupApiRoute(router, 'get', '/:uid/emails', [...middlewares, middleware.assert.user], controllers.write.users.listEmails);
setupApiRoute(router, 'post', '/:uid/emails', [...middlewares, middleware.assert.user], controllers.write.users.addEmail);
setupApiRoute(router, 'get', '/:uid/emails/:email', [...middlewares, middleware.assert.user], controllers.write.users.getEmail);
setupApiRoute(router, 'post', '/:uid/emails/:email/confirm', [...middlewares, middleware.assert.user], controllers.write.users.confirmEmail);

View File

@@ -826,7 +826,7 @@ describe('Categories', () => {
});
});
describe.only('Categories.getModeratorUids', () => {
describe('Categories.getModeratorUids', () => {
let cid;
before(async () => {
@@ -865,7 +865,7 @@ describe('Categories', () => {
const payload = {};
payload[cid] = { disabled: 1 };
await Categories.update(payload);
const uids = await Categories.getModeratorUids([1, 2]);
const uids = await Categories.getModeratorUids([cid, 2]);
assert(!uids[0].includes('1'));
});