The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/api/server/v1/users.js

936 lines
26 KiB

import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import _ from 'underscore';
import Busboy from 'busboy';
import { Users, Subscriptions } from '../../../models/server';
import { Users as UsersRaw } from '../../../models/server/raw';
import { hasPermission } from '../../../authorization';
import { settings } from '../../../settings';
import { getURL } from '../../../utils';
import {
validateCustomFields,
saveUser,
saveCustomFieldsWithoutValidation,
checkUsernameAvailability,
setUserAvatar,
saveCustomFields,
} from '../../../lib';
import { getFullUserDataByIdOrUsername } from '../../../lib/server/functions/getFullUserData';
import { API } from '../api';
import { setStatusText } from '../../../lib/server';
import { findUsersToAutocomplete, getInclusiveFields, getNonEmptyFields, getNonEmptyQuery } from '../lib/users';
import { getUserForCheck, emailCheck } from '../../../2fa/server/code';
import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey';
import { setUserStatus } from '../../../../imports/users-presence/server/activeUsers';
import { resetTOTP } from '../../../2fa/server/functions/resetTOTP';
import { Team } from '../../../../server/sdk';
API.v1.addRoute('users.create', { authRequired: true }, {
post() {
check(this.bodyParams, {
email: String,
name: String,
password: String,
username: String,
active: Match.Maybe(Boolean),
bio: Match.Maybe(String),
nickname: Match.Maybe(String),
statusText: Match.Maybe(String),
roles: Match.Maybe(Array),
joinDefaultChannels: Match.Maybe(Boolean),
requirePasswordChange: Match.Maybe(Boolean),
setRandomPassword: Match.Maybe(Boolean),
sendWelcomeEmail: Match.Maybe(Boolean),
verified: Match.Maybe(Boolean),
customFields: Match.Maybe(Object),
});
// New change made by pull request #5152
if (typeof this.bodyParams.joinDefaultChannels === 'undefined') {
this.bodyParams.joinDefaultChannels = true;
}
if (this.bodyParams.customFields) {
validateCustomFields(this.bodyParams.customFields);
}
const newUserId = saveUser(this.userId, this.bodyParams);
if (this.bodyParams.customFields) {
saveCustomFieldsWithoutValidation(newUserId, this.bodyParams.customFields);
}
if (typeof this.bodyParams.active !== 'undefined') {
Meteor.runAsUser(this.userId, () => {
Meteor.call('setUserActiveStatus', newUserId, this.bodyParams.active);
});
}
const { fields } = this.parseJsonQuery();
return API.v1.success({ user: Users.findOneById(newUserId, { fields }) });
},
});
API.v1.addRoute('users.delete', { authRequired: true }, {
post() {
if (!hasPermission(this.userId, 'delete-user')) {
return API.v1.unauthorized();
}
const user = this.getUserFromParams();
const { confirmRelinquish = false } = this.requestParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('deleteUser', user._id, confirmRelinquish);
});
return API.v1.success();
},
});
API.v1.addRoute('users.deleteOwnAccount', { authRequired: true }, {
post() {
const { password } = this.bodyParams;
if (!password) {
return API.v1.failure('Body parameter "password" is required.');
}
if (!settings.get('Accounts_AllowDeleteOwnAccount')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
const { confirmRelinquish = false } = this.requestParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('deleteUserOwnAccount', password, confirmRelinquish);
});
return API.v1.success();
},
});
API.v1.addRoute('users.getAvatar', { authRequired: false }, {
get() {
const user = this.getUserFromParams();
const url = getURL(`/avatar/${ user.username }`, { cdn: false, full: true });
this.response.setHeader('Location', url);
return {
statusCode: 307,
body: url,
};
},
});
API.v1.addRoute('users.setActiveStatus', { authRequired: true }, {
post() {
check(this.bodyParams, {
userId: String,
activeStatus: Boolean,
confirmRelinquish: Match.Maybe(Boolean),
});
if (!hasPermission(this.userId, 'edit-other-user-active-status')) {
return API.v1.unauthorized();
}
Meteor.runAsUser(this.userId, () => {
const { userId, activeStatus, confirmRelinquish = false } = this.bodyParams;
Meteor.call('setUserActiveStatus', userId, activeStatus, confirmRelinquish);
});
return API.v1.success({ user: Users.findOneById(this.bodyParams.userId, { fields: { active: 1 } }) });
},
});
API.v1.addRoute('users.deactivateIdle', { authRequired: true }, {
post() {
check(this.bodyParams, {
daysIdle: Match.Integer,
role: Match.Optional(String),
});
if (!hasPermission(this.userId, 'edit-other-user-active-status')) {
return API.v1.unauthorized();
}
const { daysIdle, role = 'user' } = this.bodyParams;
const lastLoggedIn = new Date();
lastLoggedIn.setDate(lastLoggedIn.getDate() - daysIdle);
const count = Users.setActiveNotLoggedInAfterWithRole(lastLoggedIn, role, false);
return API.v1.success({
count,
});
},
});
API.v1.addRoute('users.getPresence', { authRequired: true }, {
get() {
if (this.isUserFromParams()) {
const user = Users.findOneById(this.userId);
return API.v1.success({
presence: user.status,
connectionStatus: user.statusConnection,
lastLogin: user.lastLogin,
});
}
const user = this.getUserFromParams();
return API.v1.success({
presence: user.status,
});
},
});
API.v1.addRoute('users.info', { authRequired: true }, {
get() {
const { username, userId } = this.requestParams();
const { fields } = this.parseJsonQuery();
const user = getFullUserDataByIdOrUsername({ userId: this.userId, filterId: userId, filterUsername: username });
if (!user) {
return API.v1.failure('User not found.');
}
const myself = user._id === this.userId;
if (fields.userRooms === 1 && (myself || hasPermission(this.userId, 'view-other-user-channels'))) {
user.rooms = Subscriptions.findByUserId(user._id, {
fields: {
rid: 1,
name: 1,
t: 1,
roles: 1,
unread: 1,
},
sort: {
t: 1,
name: 1,
},
}).fetch();
}
return API.v1.success({
user,
});
},
});
API.v1.addRoute('users.list', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-d-room')) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const nonEmptyQuery = getNonEmptyQuery(query);
const nonEmptyFields = getNonEmptyFields(fields);
const inclusiveFields = getInclusiveFields(nonEmptyFields);
const actualSort = sort && sort.name ? { nameInsensitive: sort.name, ...sort } : sort || { username: 1 };
const result = Promise.await(
UsersRaw.col
.aggregate([
{
$match: nonEmptyQuery,
},
{
$project: inclusiveFields,
},
{
$addFields: {
nameInsensitive: {
$toLower: '$name',
},
},
},
{
$facet: {
sortedResults: [{
$sort: actualSort,
}, {
$skip: offset,
}, {
$limit: count,
}],
totalCount: [{ $group: { _id: null, total: { $sum: 1 } } }],
},
},
])
.toArray(),
);
const { sortedResults: users, totalCount: [{ total } = { total: 0 }] } = result[0];
return API.v1.success({
users,
count: users.length,
offset,
total,
});
},
});
API.v1.addRoute('users.register', { authRequired: false }, {
post() {
if (this.userId) {
return API.v1.failure('Logged in users can not register again.');
}
// We set their username here, so require it
// The `registerUser` checks for the other requirements
check(this.bodyParams, Match.ObjectIncluding({
username: String,
}));
if (!checkUsernameAvailability(this.bodyParams.username)) {
return API.v1.failure('Username is already in use');
}
// Register the user
const userId = Meteor.call('registerUser', this.bodyParams);
// Now set their username
Meteor.runAsUser(userId, () => Meteor.call('setUsername', this.bodyParams.username));
const { fields } = this.parseJsonQuery();
return API.v1.success({ user: Users.findOneById(userId, { fields }) });
},
});
API.v1.addRoute('users.resetAvatar', { authRequired: true }, {
post() {
const user = this.getUserFromParams();
if (user._id === this.userId) {
Meteor.runAsUser(this.userId, () => Meteor.call('resetAvatar'));
} else if (hasPermission(this.userId, 'edit-other-user-info')) {
Meteor.runAsUser(user._id, () => Meteor.call('resetAvatar'));
} else {
return API.v1.unauthorized();
}
return API.v1.success();
},
});
API.v1.addRoute('users.setAvatar', { authRequired: true }, {
post() {
check(this.bodyParams, Match.ObjectIncluding({
avatarUrl: Match.Maybe(String),
userId: Match.Maybe(String),
username: Match.Maybe(String),
}));
if (!settings.get('Accounts_AllowUserAvatarChange')) {
throw new Meteor.Error('error-not-allowed', 'Change avatar is not allowed', {
method: 'users.setAvatar',
});
}
let user;
if (this.isUserFromParams()) {
user = Meteor.users.findOne(this.userId);
} else if (hasPermission(this.userId, 'edit-other-user-avatar')) {
user = this.getUserFromParams();
} else {
return API.v1.unauthorized();
}
Meteor.runAsUser(user._id, () => {
if (this.bodyParams.avatarUrl) {
setUserAvatar(user, this.bodyParams.avatarUrl, '', 'url');
} else {
const busboy = new Busboy({ headers: this.request.headers });
const fields = {};
const getUserFromFormData = (fields) => {
if (fields.userId) {
return Users.findOneById(fields.userId, { _id: 1 });
}
if (fields.username) {
return Users.findOneByUsernameIgnoringCase(fields.username, { _id: 1 });
}
};
Meteor.wrapAsync((callback) => {
busboy.on('file', Meteor.bindEnvironment((fieldname, file, filename, encoding, mimetype) => {
if (fieldname !== 'image') {
return callback(new Meteor.Error('invalid-field'));
}
const imageData = [];
file.on('data', Meteor.bindEnvironment((data) => {
imageData.push(data);
}));
file.on('end', Meteor.bindEnvironment(() => {
const sentTheUserByFormData = fields.userId || fields.username;
if (sentTheUserByFormData) {
user = getUserFromFormData(fields);
if (!user) {
return callback(new Meteor.Error('error-invalid-user', 'The optional "userId" or "username" param provided does not match any users'));
}
const isAnotherUser = this.userId !== user._id;
if (isAnotherUser && !hasPermission(this.userId, 'edit-other-user-info')) {
return callback(new Meteor.Error('error-not-allowed', 'Not allowed'));
}
}
try {
setUserAvatar(user, Buffer.concat(imageData), mimetype, 'rest');
callback();
} catch (e) {
callback(e);
}
}));
}));
busboy.on('field', (fieldname, val) => {
fields[fieldname] = val;
});
this.request.pipe(busboy);
})();
}
});
return API.v1.success();
},
});
API.v1.addRoute('users.getStatus', { authRequired: true }, {
get() {
if (this.isUserFromParams()) {
const user = Users.findOneById(this.userId);
return API.v1.success({
_id: user._id,
message: user.statusText,
connectionStatus: user.statusConnection,
status: user.status,
});
}
const user = this.getUserFromParams();
return API.v1.success({
_id: user._id,
message: user.statusText,
status: user.status,
});
},
});
API.v1.addRoute('users.setStatus', { authRequired: true }, {
post() {
check(this.bodyParams, Match.ObjectIncluding({
status: Match.Maybe(String),
message: Match.Maybe(String),
}));
if (!settings.get('Accounts_AllowUserStatusMessageChange')) {
throw new Meteor.Error('error-not-allowed', 'Change status is not allowed', {
method: 'users.setStatus',
});
}
let user;
if (this.isUserFromParams()) {
user = Meteor.users.findOne(this.userId);
} else if (hasPermission(this.userId, 'edit-other-user-info')) {
user = this.getUserFromParams();
} else {
return API.v1.unauthorized();
}
Meteor.runAsUser(user._id, () => {
if (this.bodyParams.message || this.bodyParams.message === '') {
setStatusText(user._id, this.bodyParams.message);
}
if (this.bodyParams.status) {
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.', {
method: 'users.setStatus',
});
}
}
});
return API.v1.success();
},
});
API.v1.addRoute('users.update', { authRequired: true, twoFactorRequired: true }, {
post() {
check(this.bodyParams, {
userId: String,
data: Match.ObjectIncluding({
email: Match.Maybe(String),
name: Match.Maybe(String),
password: Match.Maybe(String),
username: Match.Maybe(String),
bio: Match.Maybe(String),
nickname: Match.Maybe(String),
statusText: Match.Maybe(String),
active: Match.Maybe(Boolean),
roles: Match.Maybe(Array),
joinDefaultChannels: Match.Maybe(Boolean),
requirePasswordChange: Match.Maybe(Boolean),
sendWelcomeEmail: Match.Maybe(Boolean),
verified: Match.Maybe(Boolean),
customFields: Match.Maybe(Object),
}),
});
const userData = _.extend({ _id: this.bodyParams.userId }, this.bodyParams.data);
Meteor.runAsUser(this.userId, () => saveUser(this.userId, userData));
if (this.bodyParams.data.customFields) {
saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields);
}
if (typeof this.bodyParams.data.active !== 'undefined') {
const { userId, data: { active }, confirmRelinquish = false } = this.bodyParams;
Meteor.runAsUser(this.userId, () => {
Meteor.call('setUserActiveStatus', userId, active, confirmRelinquish);
});
}
const { fields } = this.parseJsonQuery();
return API.v1.success({ user: Users.findOneById(this.bodyParams.userId, { fields }) });
},
});
API.v1.addRoute('users.updateOwnBasicInfo', { authRequired: true }, {
post() {
check(this.bodyParams, {
data: Match.ObjectIncluding({
email: Match.Maybe(String),
name: Match.Maybe(String),
username: Match.Maybe(String),
nickname: Match.Maybe(String),
statusText: Match.Maybe(String),
currentPassword: Match.Maybe(String),
newPassword: Match.Maybe(String),
}),
customFields: Match.Maybe(Object),
});
const userData = {
email: this.bodyParams.data.email,
realname: this.bodyParams.data.name,
username: this.bodyParams.data.username,
nickname: this.bodyParams.data.nickname,
statusText: this.bodyParams.data.statusText,
newPassword: this.bodyParams.data.newPassword,
typedPassword: this.bodyParams.data.currentPassword,
};
// saveUserProfile now uses the default two factor authentication procedures, so we need to provide that
const twoFactorOptions = !userData.typedPassword
? null
: {
twoFactorCode: userData.typedPassword,
twoFactorMethod: 'password',
};
Meteor.runAsUser(this.userId, () => Meteor.call('saveUserProfile', userData, this.bodyParams.customFields, twoFactorOptions));
return API.v1.success({ user: Users.findOneById(this.userId, { fields: API.v1.defaultFieldsToExclude }) });
},
});
API.v1.addRoute('users.createToken', { authRequired: true }, {
post() {
const user = this.getUserFromParams();
let data;
Meteor.runAsUser(this.userId, () => {
data = Meteor.call('createToken', user._id);
});
return data ? API.v1.success({ data }) : API.v1.unauthorized();
},
});
API.v1.addRoute('users.getPreferences', { authRequired: true }, {
get() {
const user = Users.findOneById(this.userId);
if (user.settings) {
const { preferences = {} } = user.settings;
preferences.language = user.language;
return API.v1.success({
preferences,
});
}
return API.v1.failure(TAPi18n.__('Accounts_Default_User_Preferences_not_available').toUpperCase());
},
});
API.v1.addRoute('users.setPreferences', { authRequired: true }, {
post() {
check(this.bodyParams, {
userId: Match.Maybe(String),
data: Match.ObjectIncluding({
newRoomNotification: Match.Maybe(String),
newMessageNotification: Match.Maybe(String),
clockMode: Match.Maybe(Number),
useEmojis: Match.Maybe(Boolean),
convertAsciiEmoji: Match.Maybe(Boolean),
saveMobileBandwidth: Match.Maybe(Boolean),
collapseMediaByDefault: Match.Maybe(Boolean),
autoImageLoad: Match.Maybe(Boolean),
emailNotificationMode: Match.Maybe(String),
unreadAlert: Match.Maybe(Boolean),
notificationsSoundVolume: Match.Maybe(Number),
desktopNotifications: Match.Maybe(String),
mobileNotifications: Match.Maybe(String),
enableAutoAway: Match.Maybe(Boolean),
highlights: Match.Maybe(Array),
desktopNotificationRequireInteraction: Match.Maybe(Boolean),
messageViewMode: Match.Maybe(Number),
showMessageInMainThread: Match.Maybe(Boolean),
hideUsernames: Match.Maybe(Boolean),
hideRoles: Match.Maybe(Boolean),
hideAvatars: Match.Maybe(Boolean),
hideFlexTab: Match.Maybe(Boolean),
sendOnEnter: Match.Maybe(String),
language: Match.Maybe(String),
sidebarShowFavorites: Match.Optional(Boolean),
sidebarShowUnread: Match.Optional(Boolean),
sidebarSortby: Match.Optional(String),
sidebarViewMode: Match.Optional(String),
sidebarHideAvatar: Match.Optional(Boolean),
sidebarGroupByType: Match.Optional(Boolean),
sidebarShowDiscussion: Match.Optional(Boolean),
muteFocusedConversations: Match.Optional(Boolean),
}),
});
if (this.bodyParams.userId && this.bodyParams.userId !== this.userId && !hasPermission(this.userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Editing user is not allowed');
}
const userId = this.bodyParams.userId ? this.bodyParams.userId : this.userId;
if (!Users.findOneById(userId)) {
throw new Meteor.Error('error-invalid-user', 'The optional "userId" param provided does not match any users');
}
Meteor.runAsUser(userId, () => Meteor.call('saveUserPreferences', this.bodyParams.data));
const user = Users.findOneById(userId, {
fields: {
'settings.preferences': 1,
language: 1,
},
});
return API.v1.success({
user: {
_id: user._id,
settings: {
preferences: {
...user.settings.preferences,
language: user.language,
},
},
},
});
},
});
API.v1.addRoute('users.forgotPassword', { authRequired: false }, {
post() {
const { email } = this.bodyParams;
if (!email) {
return API.v1.failure('The \'email\' param is required');
}
Meteor.call('sendForgotPasswordEmail', email);
return API.v1.success();
},
});
API.v1.addRoute('users.getUsernameSuggestion', { authRequired: true }, {
get() {
const result = Meteor.runAsUser(this.userId, () => Meteor.call('getUsernameSuggestion'));
return API.v1.success({ result });
},
});
API.v1.addRoute('users.generatePersonalAccessToken', { authRequired: true, twoFactorRequired: true }, {
post() {
const { tokenName, bypassTwoFactor } = this.bodyParams;
if (!tokenName) {
return API.v1.failure('The \'tokenName\' param is required');
}
const token = Meteor.runAsUser(this.userId, () => Meteor.call('personalAccessTokens:generateToken', { tokenName, bypassTwoFactor }));
return API.v1.success({ token });
},
});
API.v1.addRoute('users.regeneratePersonalAccessToken', { authRequired: true, twoFactorRequired: true }, {
post() {
const { tokenName } = this.bodyParams;
if (!tokenName) {
return API.v1.failure('The \'tokenName\' param is required');
}
const token = Meteor.runAsUser(this.userId, () => Meteor.call('personalAccessTokens:regenerateToken', { tokenName }));
return API.v1.success({ token });
},
});
API.v1.addRoute('users.getPersonalAccessTokens', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'create-personal-access-tokens')) {
throw new Meteor.Error('not-authorized', 'Not Authorized');
}
const loginTokens = Users.getLoginTokensByUserId(this.userId).fetch()[0];
const getPersonalAccessTokens = () => loginTokens.services.resume.loginTokens
.filter((loginToken) => loginToken.type && loginToken.type === 'personalAccessToken')
.map((loginToken) => ({
name: loginToken.name,
createdAt: loginToken.createdAt,
lastTokenPart: loginToken.lastTokenPart,
bypassTwoFactor: loginToken.bypassTwoFactor,
}));
return API.v1.success({
tokens: loginTokens ? getPersonalAccessTokens() : [],
});
},
});
API.v1.addRoute('users.removePersonalAccessToken', { authRequired: true, twoFactorRequired: true }, {
post() {
const { tokenName } = this.bodyParams;
if (!tokenName) {
return API.v1.failure('The \'tokenName\' param is required');
}
Meteor.runAsUser(this.userId, () => Meteor.call('personalAccessTokens:removeToken', {
tokenName,
}));
return API.v1.success();
},
});
API.v1.addRoute('users.2fa.enableEmail', { authRequired: true }, {
post() {
Users.enableEmail2FAByUserId(this.userId);
return API.v1.success();
},
});
API.v1.addRoute('users.2fa.disableEmail', { authRequired: true, twoFactorRequired: true, twoFactorOptions: { disableRememberMe: true } }, {
post() {
Users.disableEmail2FAByUserId(this.userId);
return API.v1.success();
},
});
API.v1.addRoute('users.2fa.sendEmailCode', {
post() {
const { emailOrUsername } = this.bodyParams;
if (!emailOrUsername) {
throw new Meteor.Error('error-parameter-required', 'emailOrUsername is required');
}
const method = emailOrUsername.includes('@') ? 'findOneByEmailAddress' : 'findOneByUsername';
const userId = this.userId || Users[method](emailOrUsername, { fields: { _id: 1 } })?._id;
if (!userId) {
this.logger.error('[2fa] User was not found when requesting 2fa email code');
return API.v1.success();
}
emailCheck.sendEmailCode(getUserForCheck(userId));
return API.v1.success();
},
});
API.v1.addRoute('users.presence', { authRequired: true }, {
get() {
const { from, ids } = this.queryParams;
const options = {
fields: {
username: 1,
name: 1,
status: 1,
utcOffset: 1,
statusText: 1,
avatarETag: 1,
},
};
if (ids) {
return API.v1.success({
users: Users.findNotOfflineByIds(Array.isArray(ids) ? ids : ids.split(','), options).fetch(),
full: false,
});
}
if (from) {
const ts = new Date(from);
const diff = (Date.now() - ts) / 1000 / 60;
if (diff < 10) {
return API.v1.success({
users: Users.findNotIdUpdatedFrom(this.userId, ts, options).fetch(),
full: false,
});
}
}
return API.v1.success({
users: Users.findUsersNotOffline(options).fetch(),
full: true,
});
},
});
API.v1.addRoute('users.requestDataDownload', { authRequired: true }, {
get() {
const { fullExport = false } = this.queryParams;
const result = Meteor.runAsUser(this.userId, () => Meteor.call('requestDataDownload', { fullExport: fullExport === 'true' }));
return API.v1.success({
requested: result.requested,
exportOperation: result.exportOperation,
});
},
});
API.v1.addRoute('users.logoutOtherClients', { authRequired: true }, {
post() {
try {
const result = Meteor.call('logoutOtherClients');
return API.v1.success(result);
} catch (error) {
return API.v1.failure(error);
}
},
});
API.v1.addRoute('users.autocomplete', { authRequired: true }, {
get() {
const { selector } = this.queryParams;
if (!selector) {
return API.v1.failure('The \'selector\' param is required');
}
return API.v1.success(Promise.await(findUsersToAutocomplete({
uid: this.userId,
selector: JSON.parse(selector),
})));
},
});
API.v1.addRoute('users.removeOtherTokens', { authRequired: true }, {
post() {
API.v1.success(Meteor.call('removeOtherTokens'));
},
});
API.v1.addRoute('users.resetE2EKey', { authRequired: true, twoFactorRequired: true, twoFactorOptions: { disableRememberMe: true } }, {
post() {
// reset own keys
if (this.isUserFromParams()) {
resetUserE2EEncriptionKey(this.userId, false);
return API.v1.success();
}
// reset other user keys
const user = this.getUserFromParams();
if (!user) {
throw new Meteor.Error('error-invalid-user-id', 'Invalid user id');
}
if (!settings.get('Accounts_TwoFactorAuthentication_Enforce_Password_Fallback')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
if (!hasPermission(Meteor.userId(), 'edit-other-user-e2ee')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
if (!resetUserE2EEncriptionKey(user._id, true)) {
return API.v1.failure();
}
return API.v1.success();
},
});
API.v1.addRoute('users.resetTOTP', { authRequired: true, twoFactorRequired: true, twoFactorOptions: { disableRememberMe: true } }, {
post() {
// reset own keys
if (this.isUserFromParams()) {
Promise.await(resetTOTP(this.userId, false));
return API.v1.success();
}
// reset other user keys
const user = this.getUserFromParams();
if (!user) {
throw new Meteor.Error('error-invalid-user-id', 'Invalid user id');
}
if (!settings.get('Accounts_TwoFactorAuthentication_Enforce_Password_Fallback')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
if (!hasPermission(Meteor.userId(), 'edit-other-user-totp')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
Promise.await(resetTOTP(user._id, true));
return API.v1.success();
},
});
API.v1.addRoute('users.listTeams', { authRequired: true }, {
get() {
const { userId } = this.bodyParams;
// If the caller has permission to view all teams, there's no need to filter the teams
const adminId = hasPermission(this.userId, 'view-all-teams') ? '' : this.userId;
const teams = Promise.await(Team.findBySubscribedUserIds(userId, adminId));
return API.v1.success({
teams,
});
},
});