Skip to content

Commit 49cc2c9

Browse files
[IMPROVE] Add error messages to the creation of channels or usernames containing reserved words (RocketChat#21016)
Co-authored-by: Diego Sampaio <chinello@gmail.com>
1 parent 342dbec commit 49cc2c9

9 files changed

Lines changed: 133 additions & 2 deletions

File tree

app/lib/server/functions/checkUsernameAvailability.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import s from 'underscore.string';
44
import { escapeRegExp } from '../../../../lib/escapeRegExp';
55
import { settings } from '../../../settings';
66
import { Team } from '../../../../server/sdk';
7+
import { validateName } from './validateName';
78

89
let usernameBlackList = [];
910

@@ -17,7 +18,7 @@ const usernameIsBlocked = (username, usernameBlackList) => usernameBlackList.len
1718
&& usernameBlackList.some((restrictedUsername) => restrictedUsername.test(s.trim(escapeRegExp(username))));
1819

1920
export const checkUsernameAvailability = function(username) {
20-
if (usernameIsBlocked(username, usernameBlackList)) {
21+
if (usernameIsBlocked(username, usernameBlackList) || !validateName(username)) {
2122
return false;
2223
}
2324

app/lib/server/functions/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ export { _setUsername, setUsername } from './setUsername';
3333
export { unarchiveRoom } from './unarchiveRoom';
3434
export { updateMessage } from './updateMessage';
3535
export { validateCustomFields } from './validateCustomFields';
36+
export { validateName } from './validateName';
3637
export { getAvatarSuggestionForUser } from './getAvatarSuggestionForUser';

app/lib/server/functions/saveUserIdentity.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { setRealName } from './setRealName';
33
import { Messages, Rooms, Subscriptions, LivechatDepartmentAgents, Users } from '../../../models/server';
44
import { FileUpload } from '../../../file-upload/server';
55
import { updateGroupDMsName } from './updateGroupDMsName';
6+
import { validateName } from './validateName';
67

78
/**
89
*
@@ -25,6 +26,10 @@ export function saveUserIdentity(userId, { _id, name: rawName, username: rawUser
2526
const usernameChanged = previousUsername !== username;
2627

2728
if (typeof rawUsername !== 'undefined' && usernameChanged) {
29+
if (!validateName(username)) {
30+
return false;
31+
}
32+
2833
if (!setUsername(_id, username, user)) {
2934
return false;
3035
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { settings } from '../../../settings/server';
2+
3+
export const validateName = function(name: string): boolean {
4+
const blockedNames = settings.get('Accounts_SystemBlockedUsernameList');
5+
if (!blockedNames || typeof blockedNames !== 'string') {
6+
return true;
7+
}
8+
9+
if (blockedNames.split(',').includes(name.toLowerCase())) {
10+
return false;
11+
}
12+
13+
return true;
14+
};

app/lib/server/startup/settings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ settings.addGroup('Accounts', function() {
164164
this.add('Accounts_BlockedUsernameList', '', {
165165
type: 'string',
166166
});
167+
this.add('Accounts_SystemBlockedUsernameList', 'admin,administrator,system,user', {
168+
type: 'string',
169+
hidden: true,
170+
});
167171
this.add('Accounts_UseDefaultBlockedDomainsList', true, {
168172
type: 'boolean',
169173
});

app/utils/lib/getValidRoomName.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import limax from 'limax';
33

44
import { settings } from '../../settings';
55
import { Rooms } from '../../models';
6+
import { validateName } from '../../lib/server/functions/validateName';
67

78
export const getValidRoomName = (displayName, rid = '', options = {}) => {
89
let slugifiedName = displayName;
@@ -33,7 +34,7 @@ export const getValidRoomName = (displayName, rid = '', options = {}) => {
3334
}
3435
}
3536

36-
if (!nameValidation.test(slugifiedName)) {
37+
if (!nameValidation.test(slugifiedName) || !validateName(slugifiedName)) {
3738
throw new Meteor.Error('error-invalid-room-name', `${ slugifiedName } is not a valid room name.`, {
3839
function: 'RocketChat.getValidRoomName',
3940
channel_name: slugifiedName,

tests/data/api-data.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export const apiRoleNameSubscriptions = `api${ roleNameSubscriptions }`;
2121
export const apiRoleScopeUsers = `${ roleScopeUsers }`;
2222
export const apiRoleScopeSubscriptions = `${ roleScopeSubscriptions }`;
2323
export const apiRoleDescription = `api${ roleDescription }`;
24+
export const reservedWords = [
25+
'admin',
26+
'administrator',
27+
'system',
28+
'user',
29+
];
2430

2531
export const targetUser = {};
2632
export const channel = {};

tests/end-to-end/api/01-users.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
targetUser,
1313
log,
1414
wait,
15+
reservedWords,
1516
} from '../../data/api-data.js';
1617
import { adminEmail, preferences, password, adminUsername } from '../../data/user.js';
1718
import { imgURL } from '../../data/interactions.js';
@@ -156,6 +157,30 @@ describe('[Users]', function() {
156157
});
157158
});
158159

160+
function failCreateUser(name) {
161+
it(`should not create a new user if username is the reserved word ${ name }`, (done) => {
162+
request.post(api('users.create'))
163+
.set(credentials)
164+
.send({
165+
email: `create_user_fail_${ apiEmail }`,
166+
name: `create_user_fail_${ apiUsername }`,
167+
username: name,
168+
password,
169+
active: true,
170+
roles: ['user'],
171+
joinDefaultChannels: true,
172+
verified: true,
173+
})
174+
.expect('Content-Type', 'application/json')
175+
.expect(400)
176+
.expect((res) => {
177+
expect(res.body).to.have.property('success', false);
178+
expect(res.body).to.have.property('error', `${ name } is already in use :( [error-field-unavailable]`);
179+
})
180+
.end(done);
181+
});
182+
}
183+
159184
function failUserWithCustomField(field) {
160185
it(`should not create a user if a custom field ${ field.reason }`, (done) => {
161186
setCustomFields({ customFieldText }, (error) => {
@@ -197,6 +222,10 @@ describe('[Users]', function() {
197222
].forEach((field) => {
198223
failUserWithCustomField(field);
199224
});
225+
226+
reservedWords.forEach((name) => {
227+
failCreateUser(name);
228+
});
200229
});
201230

202231
describe('[/users.register]', () => {
@@ -1073,6 +1102,30 @@ describe('[Users]', function() {
10731102
});
10741103
});
10751104
});
1105+
1106+
function failUpdateUser(name) {
1107+
it(`should not update an user if the new username is the reserved word ${ name }`, (done) => {
1108+
request.post(api('users.update'))
1109+
.set(credentials)
1110+
.send({
1111+
userId: targetUser._id,
1112+
data: {
1113+
username: name,
1114+
},
1115+
})
1116+
.expect('Content-Type', 'application/json')
1117+
.expect(400)
1118+
.expect((res) => {
1119+
expect(res.body).to.have.property('success', false);
1120+
expect(res.body).to.have.property('error', 'Could not save user identity [error-could-not-save-identity]');
1121+
})
1122+
.end(done);
1123+
});
1124+
}
1125+
1126+
reservedWords.forEach((name) => {
1127+
failUpdateUser(name);
1128+
});
10761129
});
10771130

10781131
describe('[/users.updateOwnBasicInfo]', () => {
@@ -1238,6 +1291,29 @@ describe('[Users]', function() {
12381291
})
12391292
.end(done);
12401293
});
1294+
1295+
function failUpdateUserOwnBasicInfo(name) {
1296+
it(`should not update an user's basic info if the new username is the reserved word ${ name }`, (done) => {
1297+
request.post(api('users.updateOwnBasicInfo'))
1298+
.set(credentials)
1299+
.send({
1300+
data: {
1301+
username: name,
1302+
},
1303+
})
1304+
.expect('Content-Type', 'application/json')
1305+
.expect(400)
1306+
.expect((res) => {
1307+
expect(res.body).to.have.property('success', false);
1308+
expect(res.body).to.have.property('error', 'Could not save user identity [error-could-not-save-identity]');
1309+
})
1310+
.end(done);
1311+
});
1312+
}
1313+
1314+
reservedWords.forEach((name) => {
1315+
failUpdateUserOwnBasicInfo(name);
1316+
});
12411317
});
12421318

12431319
describe('[/users.setPreferences]', () => {

tests/end-to-end/api/02-channels.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
credentials,
88
apiPublicChannelName,
99
channel,
10+
reservedWords,
1011
} from '../../data/api-data.js';
1112
import { adminUsername, password } from '../../data/user.js';
1213
import { createUser, login } from '../../data/users.helper';
@@ -836,6 +837,28 @@ describe('[Channels]', function() {
836837
it('/channels.rename', async () => {
837838
const roomInfo = await getRoomInfo(channel._id);
838839

840+
function failRenameChannel(name) {
841+
it(`should not rename a channel to the reserved name ${ name }`, (done) => {
842+
request.post(api('channels.rename'))
843+
.set(credentials)
844+
.send({
845+
roomId: channel._id,
846+
name,
847+
})
848+
.expect('Content-Type', 'application/json')
849+
.expect(400)
850+
.expect((res) => {
851+
expect(res.body).to.have.property('success', false);
852+
expect(res.body).to.have.property('error', `${ name } is already in use :( [error-field-unavailable]`);
853+
})
854+
.end(done);
855+
});
856+
}
857+
858+
reservedWords.forEach((name) => {
859+
failRenameChannel(name);
860+
});
861+
839862
return request.post(api('channels.rename'))
840863
.set(credentials)
841864
.send({

0 commit comments

Comments
 (0)