[NEW] Option to prevent users from using Invisible status (#20084)

Co-authored-by: Diego Sampaio <chinello@gmail.com>
pull/21963/head^2
Lucas Sartor Chauvin 4 years ago committed by GitHub
parent 657dfaa74b
commit 525f451628
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/api/server/v1/users.js
  2. 6
      app/lib/server/startup/settings.js
  3. 30
      app/ui/client/lib/userPopoverStatus.js
  4. 7
      app/user-status/server/methods/setUserStatus.js
  5. 14
      client/components/UserStatusMenu.js
  6. 8
      client/sidebar/header/UserDropdown.js
  7. 1
      packages/rocketchat-i18n/i18n/en.i18n.json
  8. 1
      packages/rocketchat-i18n/i18n/pt-BR.i18n.json
  9. 29
      tests/end-to-end/api/01-users.js

@ -456,12 +456,20 @@ API.v1.addRoute('users.setStatus', { authRequired: true }, {
const validStatus = ['online', 'away', 'offline', 'busy'];
if (validStatus.includes(this.bodyParams.status)) {
const { status } = this.bodyParams;
if (status === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
method: 'users.setStatus',
});
}
Meteor.users.update(user._id, {
$set: {
status,
statusDefault: status,
},
});
setUserStatus(user, status);
} else {
throw new Meteor.Error('error-invalid-status', 'Valid status types include online, away, offline, and busy.', {

@ -118,6 +118,12 @@ settings.addGroup('Accounts', function() {
],
public: true,
});
this.add('Accounts_AllowInvisibleStatusOption', true, {
type: 'boolean',
public: true,
i18nLabel: 'Accounts_AllowInvisibleStatusOption',
});
this.section('Registration', function() {
this.add('Accounts_Send_Email_When_Activating', true, {
type: 'boolean',

@ -1,12 +1,8 @@
import { t } from '../../../utils';
import { settings } from '../../../settings/client';
export const getPopoverStatusConfig = (currentTarget, actionCallback) => ({
popoverClass: 'edit-status-type',
columns: [
{
groups: [
{
items: [
export const getPopoverStatusConfig = (currentTarget, actionCallback) => {
const items = [
{
icon: 'circle',
name: t('Online'),
@ -37,7 +33,10 @@ export const getPopoverStatusConfig = (currentTarget, actionCallback) => ({
$(currentTarget).prop('class', 'rc-input__icon js-status-type edit-status-type-icon--busy');
},
},
{
];
if (settings.get('Accounts_AllowInvisibleStatusOption')) {
items.push({
icon: 'circle',
name: t('Invisible'),
modifier: 'offline',
@ -46,12 +45,21 @@ export const getPopoverStatusConfig = (currentTarget, actionCallback) => ({
$('input[name=statusType]').val('offline');
$(currentTarget).prop('class', 'rc-input__icon js-status-type edit-status-type-icon--offline');
},
},
],
});
}
return {
popoverClass: 'edit-status-type',
columns: [
{
groups: [
{
items,
},
],
},
],
currentTarget,
offsetVertical: currentTarget.clientHeight,
});
};
};

@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { settings } from '../../../settings';
import { RateLimiter, setStatusText } from '../../../lib';
import { settings } from '../../../settings/server';
import { RateLimiter, setStatusText } from '../../../lib/server';
Meteor.methods({
setUserStatus(statusType, statusText) {
@ -12,6 +12,9 @@ Meteor.methods({
}
if (statusType) {
if (statusType === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', { method: 'setUserStatus' });
}
Meteor.call('UserPresence:setDefaultStatus', statusType);
}

@ -1,6 +1,7 @@
import { Button, PositionAnimated, Options, useCursor, Box } from '@rocket.chat/fuselage';
import React, { useRef, useCallback, useState, useMemo, useEffect } from 'react';
import { useSetting } from '../contexts/SettingsContext';
import { useTranslation } from '../contexts/TranslationContext';
import { UserStatus } from './UserStatus';
@ -15,6 +16,8 @@ const UserStatusMenu = ({
const [status, setStatus] = useState(initialStatus);
const allowInvisibleStatus = useSetting('Accounts_AllowInvisibleStatusOption');
const options = useMemo(() => {
const renderOption = (status, label) => (
<Box display='flex' flexDirection='row' alignItems='center'>
@ -25,13 +28,18 @@ const UserStatusMenu = ({
</Box>
);
return [
const statuses = [
['online', renderOption('online', t('Online'))],
['busy', renderOption('busy', t('Busy'))],
['away', renderOption('away', t('Away'))],
['offline', renderOption('offline', t('Invisible'))],
];
}, [t]);
if (allowInvisibleStatus) {
statuses.push(['offline', renderOption('offline', t('Invisible'))]);
}
return statuses;
}, [t, allowInvisibleStatus]);
const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = useCursor(
-1,

@ -54,6 +54,10 @@ const UserDropdown = ({ user, onClose }) => {
const { name, username, avatarETag, status, statusText } = user;
const useRealName = useSetting('UI_Use_Real_Name');
const filterInvisibleStatus = !useSetting('Accounts_AllowInvisibleStatusOption')
? (key) => userStatus.list[key].name !== 'invisible'
: () => true;
const showAdmin = useAtLeastOnePermission(ADMIN_PERMISSIONS);
const handleCustomStatus = useMutableCallback((e) => {
@ -131,7 +135,9 @@ const UserDropdown = ({ user, onClose }) => {
<Box pi='x16' fontScale='c1' textTransform='uppercase'>
{t('Status')}
</Box>
{Object.keys(userStatus.list).map((key, i) => {
{Object.keys(userStatus.list)
.filter(filterInvisibleStatus)
.map((key, i) => {
const status = userStatus.list[key];
const name = status.localizeName ? t(status.name) : status.name;
const modifier = status.statusType || user.status;

@ -42,6 +42,7 @@
"Accounts_AllowDeleteOwnAccount": "Allow Users to Delete Own Account",
"Accounts_AllowedDomainsList": "Allowed Domains List",
"Accounts_AllowedDomainsList_Description": "Comma-separated list of allowed domains",
"Accounts_AllowInvisibleStatusOption": "Allow Invisible status option",
"Accounts_AllowEmailChange": "Allow Email Change",
"Accounts_AllowEmailNotifications": "Allow Email Notifications",
"Accounts_AllowPasswordChange": "Allow Password Change",

@ -34,6 +34,7 @@
"Accounts_AllowDeleteOwnAccount": "Permitir que Usuários Apaguem A Própria Conta",
"Accounts_AllowedDomainsList": "Lista de Domínios Permitidos",
"Accounts_AllowedDomainsList_Description": "Lista de domínios permitidos, separados por vírgula",
"Accounts_AllowInvisibleStatusOption": "Permitir opção de status Invisível",
"Accounts_AllowEmailChange": "Permitir alterar e-mail",
"Accounts_AllowEmailNotifications": "Permitir notificações por e-mail",
"Accounts_AllowPasswordChange": "Permitir Alteração de Senha",

@ -2776,13 +2776,14 @@ describe('[Users]', function() {
describe('[/users.setStatus]', () => {
let user;
before((done) => {
createUser()
.then((createdUser) => {
user = createdUser;
done();
before(async () => {
user = await createUser();
});
after(async () => {
await deleteUser(user);
user = undefined;
});
it('should return an error when the setting "Accounts_AllowUserStatusMessageChange" is disabled', (done) => {
updateSetting('Accounts_AllowUserStatusMessageChange', false).then(() => {
request.post(api('users.setStatus'))
@ -2872,6 +2873,24 @@ describe('[Users]', function() {
})
.end(done);
});
it('should return an error when user changes status to offline and "Accounts_AllowInvisibleStatusOption" is disabled', async () => {
await updateSetting('Accounts_AllowInvisibleStatusOption', false);
await request.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'offline',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-status-not-allowed');
expect(res.body.error).to.be.equal('Invisible status is disabled [error-status-not-allowed]');
});
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
});
});
describe('[/users.removeOtherTokens]', () => {

Loading…
Cancel
Save