snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
40
apps/meteor/server/meteor-methods/auth/addOAuthService.ts
Normal file
40
apps/meteor/server/meteor-methods/auth/addOAuthService.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { check } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { addOAuthService } from '../../lib/oauth/addOAuthService';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
addOAuthService(name: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
export const addOAuthServiceMethod = async (userId: string, name: string): Promise<void> => {
|
||||
if ((await hasPermissionAsync(userId, 'add-oauth-service')) !== true) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Adding OAuth Services is not allowed', {
|
||||
method: 'addOAuthService',
|
||||
action: 'Adding_OAuth_Services',
|
||||
});
|
||||
}
|
||||
|
||||
await addOAuthService(name);
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async addOAuthService(name) {
|
||||
methodDeprecationLogger.method('addOAuthService', '9.0.0', '/v1/settings.addCustomOAuth');
|
||||
check(name, String);
|
||||
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'addOAuthService' });
|
||||
}
|
||||
|
||||
return addOAuthServiceMethod(userId, name);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { addPermissionToRoleMethod } from '../../lib/authorization/permissionRole';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'authorization:addPermissionToRole'(permissionId: string, role: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async 'authorization:addPermissionToRole'(permissionId, role) {
|
||||
methodDeprecationLogger.method('authorization:addPermissionToRole', '9.0.0', '/v1/permissions.addRole');
|
||||
const uid = Meteor.userId();
|
||||
if (!uid) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'authorization:addPermissionToRole' });
|
||||
}
|
||||
await addPermissionToRoleMethod(uid, permissionId, role);
|
||||
},
|
||||
});
|
||||
17
apps/meteor/server/meteor-methods/auth/addSamlService.ts
Normal file
17
apps/meteor/server/meteor-methods/auth/addSamlService.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { addSamlService } from '../../lib/saml/lib/settings';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
addSamlService(name: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
addSamlService(name) {
|
||||
addSamlService(name);
|
||||
},
|
||||
});
|
||||
73
apps/meteor/server/meteor-methods/auth/addUserToRole.ts
Normal file
73
apps/meteor/server/meteor-methods/auth/addUserToRole.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { api } from '@rocket.chat/core-services';
|
||||
import type { IRole, IUser } from '@rocket.chat/core-typings';
|
||||
import { Roles, Users } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { addUserRolesAsync } from '../../lib/roles/addUserRoles';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
export const addUserToRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
|
||||
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
|
||||
method: 'authorization:addUserToRole',
|
||||
action: 'Accessing_permissions',
|
||||
});
|
||||
}
|
||||
|
||||
if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
|
||||
method: 'authorization:addUserToRole',
|
||||
});
|
||||
}
|
||||
|
||||
const role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
|
||||
|
||||
if (!role) {
|
||||
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
|
||||
method: 'authorization:addUserToRole',
|
||||
});
|
||||
}
|
||||
|
||||
if (role._id === 'admin' && !(await hasPermissionAsync(userId, 'assign-admin-role'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', {
|
||||
method: 'authorization:addUserToRole',
|
||||
action: 'Assign_admin',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await Users.findOneByUsernameIgnoringCase(username, {
|
||||
projection: {
|
||||
_id: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?._id) {
|
||||
throw new Meteor.Error('error-user-not-found', 'User not found', {
|
||||
method: 'authorization:addUserToRole',
|
||||
});
|
||||
}
|
||||
|
||||
// verify if user can be added to given scope
|
||||
if (scope && !(await Roles.canAddUserToRole(user._id, role._id, scope))) {
|
||||
throw new Meteor.Error('error-invalid-user', 'User is not part of given room', {
|
||||
method: 'authorization:addUserToRole',
|
||||
});
|
||||
}
|
||||
|
||||
const add = await addUserRolesAsync(user._id, [role._id], scope);
|
||||
|
||||
if (settings.get('UI_DisplayRoles')) {
|
||||
void api.broadcast('user.roleUpdate', {
|
||||
type: 'added',
|
||||
_id: role._id,
|
||||
u: {
|
||||
_id: user._id,
|
||||
username,
|
||||
},
|
||||
scope,
|
||||
});
|
||||
}
|
||||
|
||||
return add;
|
||||
};
|
||||
28
apps/meteor/server/meteor-methods/auth/afterVerifyEmail.ts
Normal file
28
apps/meteor/server/meteor-methods/auth/afterVerifyEmail.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { runAfterVerifyEmail } from '../../lib/users/runAfterVerifyEmail';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
afterVerifyEmail(): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async afterVerifyEmail() {
|
||||
methodDeprecationLogger.method('afterVerifyEmail', '9.0.0', '/v1/users.verifyEmail');
|
||||
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'afterVerifyEmail',
|
||||
});
|
||||
}
|
||||
|
||||
await runAfterVerifyEmail(userId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { codesRemainingTotp } from '../../lib/2fa/functions/totp';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'2fa:checkCodesRemaining': () => { remaining: number };
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async '2fa:checkCodesRemaining'() {
|
||||
methodDeprecationLogger.method('2fa:checkCodesRemaining', '9.0.0', '/v1/users.totpCodesRemaining');
|
||||
return codesRemainingTotp(Meteor.userId());
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { check } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { settings } from '../../settings';
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
checkRegistrationSecretURL(hash) {
|
||||
check(hash, String);
|
||||
|
||||
return hash === settings.get('Accounts_RegistrationForm_SecretURL');
|
||||
},
|
||||
});
|
||||
32
apps/meteor/server/meteor-methods/auth/createToken.ts
Normal file
32
apps/meteor/server/meteor-methods/auth/createToken.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Authorization, MeteorError, User } from '@rocket.chat/core-services';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
createToken(userId: string): { userId: string; authToken: string };
|
||||
}
|
||||
}
|
||||
|
||||
const { CREATE_TOKENS_FOR_USERS_SECRET } = process.env;
|
||||
|
||||
export async function generateAccessToken(userId: string, secret: string, caller: IUser) {
|
||||
if (secret !== CREATE_TOKENS_FOR_USERS_SECRET) {
|
||||
throw new MeteorError('error-not-authorized', 'Not authorized');
|
||||
}
|
||||
|
||||
if (caller._id !== userId && !(await Authorization.hasPermission(caller, 'user-generate-access-token'))) {
|
||||
throw new MeteorError('error-not-authorized', 'Not authorized');
|
||||
}
|
||||
|
||||
const token = Accounts._generateStampedLoginToken();
|
||||
await Accounts._insertLoginToken(userId, token);
|
||||
|
||||
await User.ensureLoginTokensLimit(userId);
|
||||
|
||||
return {
|
||||
userId,
|
||||
authToken: token.token,
|
||||
};
|
||||
}
|
||||
81
apps/meteor/server/meteor-methods/auth/crowd.ts
Normal file
81
apps/meteor/server/meteor-methods/auth/crowd.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import type { TranslationKey } from '@rocket.chat/ui-contexts';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { CROWD } from '../../lib/auth-providers/crowd/crowd';
|
||||
import { logger } from '../../lib/auth-providers/crowd/logger';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
crowd_test_connection(): { message: TranslationKey; params: string[] };
|
||||
crowd_sync_users(): { message: string; params: string[] };
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async crowd_test_connection() {
|
||||
const user = await Meteor.userAsync();
|
||||
if (!user) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'crowd_test_connection',
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(user, 'test-admin-options'))) {
|
||||
throw new Meteor.Error('error-not-authorized', 'Not authorized', {
|
||||
method: 'crowd_test_connection',
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.get('CROWD_Enable') !== true) {
|
||||
throw new Meteor.Error('crowd_disabled');
|
||||
}
|
||||
|
||||
try {
|
||||
const crowd = new CROWD();
|
||||
await crowd.checkConnection();
|
||||
|
||||
return {
|
||||
message: 'Crowd_Connection_successful' as const,
|
||||
params: [],
|
||||
};
|
||||
} catch (err) {
|
||||
logger.error({
|
||||
msg: 'Invalid crowd connection details, check the url and application username/password and make sure this server is allowed to speak to crowd',
|
||||
err,
|
||||
});
|
||||
throw new Meteor.Error('Invalid connection details', '', { method: 'crowd_test_connection' });
|
||||
}
|
||||
},
|
||||
async crowd_sync_users() {
|
||||
const user = await Meteor.userAsync();
|
||||
if (settings.get('CROWD_Enable') !== true) {
|
||||
throw new Meteor.Error('crowd_disabled');
|
||||
}
|
||||
|
||||
if (!user || !(await hasPermissionAsync(user, 'sync-auth-services-users'))) {
|
||||
throw new Meteor.Error('error-not-authorized', 'Not authorized', {
|
||||
method: 'crowd_sync_users',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const crowd = new CROWD();
|
||||
const startTime = Date.now();
|
||||
await crowd.sync();
|
||||
const stopTime = Date.now();
|
||||
const actual = Math.ceil((stopTime - startTime) / 1000);
|
||||
|
||||
return {
|
||||
message: `User data synced in ${actual} seconds`,
|
||||
params: [],
|
||||
};
|
||||
} catch (err) {
|
||||
logger.error({ msg: 'Error syncing user data. ', err });
|
||||
throw new Meteor.Error('Error syncing user data', '', { method: 'crowd_sync_users' });
|
||||
}
|
||||
},
|
||||
});
|
||||
43
apps/meteor/server/meteor-methods/auth/deleteOAuthApp.ts
Normal file
43
apps/meteor/server/meteor-methods/auth/deleteOAuthApp.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { IOAuthApps } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { OAuthAccessTokens, OAuthApps, OAuthAuthCodes } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
deleteOAuthApp(applicationId: IOAuthApps['_id']): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteOAuthApp = async (userId: string, applicationId: IOAuthApps['_id']): Promise<boolean> => {
|
||||
if (!(await hasPermissionAsync(userId, 'manage-oauth-apps'))) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteOAuthApp' });
|
||||
}
|
||||
|
||||
const application = await OAuthApps.findOneAndDeleteById(applicationId, { projection: { clientId: 1 } });
|
||||
if (!application) {
|
||||
throw new Meteor.Error('error-application-not-found', 'Application not found', {
|
||||
method: 'deleteOAuthApp',
|
||||
});
|
||||
}
|
||||
|
||||
await OAuthAccessTokens.deleteMany({ clientId: application.clientId });
|
||||
await OAuthAuthCodes.deleteMany({ clientId: application.clientId });
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async deleteOAuthApp(applicationId) {
|
||||
methodDeprecationLogger.method('deleteOAuthApp', '9.0.0', '/v1/oauth-apps.delete');
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'deleteOAuthApp' });
|
||||
}
|
||||
|
||||
return deleteOAuthApp(this.userId, applicationId);
|
||||
},
|
||||
});
|
||||
19
apps/meteor/server/meteor-methods/auth/disable.ts
Normal file
19
apps/meteor/server/meteor-methods/auth/disable.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { disableTotp } from '../../lib/2fa/functions/totp';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'2fa:disable': (code: string) => Promise<boolean>;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async '2fa:disable'(code) {
|
||||
methodDeprecationLogger.method('2fa:disable', '9.0.0', '/v1/users.disableTotp');
|
||||
return disableTotp(Meteor.userId(), code);
|
||||
},
|
||||
});
|
||||
19
apps/meteor/server/meteor-methods/auth/enable.ts
Normal file
19
apps/meteor/server/meteor-methods/auth/enable.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { enableTotp } from '../../lib/2fa/functions/totp';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'2fa:enable': () => Promise<{ secret: string; url: string }>;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async '2fa:enable'() {
|
||||
methodDeprecationLogger.method('2fa:enable', '9.0.0', '/v1/users.enableTotp');
|
||||
return enableTotp(Meteor.userId());
|
||||
},
|
||||
});
|
||||
29
apps/meteor/server/meteor-methods/auth/logoutCleanUp.ts
Normal file
29
apps/meteor/server/meteor-methods/auth/logoutCleanUp.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { AppEvents, Apps } from '@rocket.chat/apps';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { check } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { afterLogoutCleanUpCallback } from '../../lib/callbacks/afterLogoutCleanUpCallback';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
logoutCleanUp(user: IUser): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async logoutCleanUp(user) {
|
||||
methodDeprecationLogger.method('logoutCleanUp', '9.0.0', '/v1/users.logout');
|
||||
check(user, Object);
|
||||
|
||||
setImmediate(() => {
|
||||
void afterLogoutCleanUpCallback.run(user);
|
||||
});
|
||||
|
||||
// App IPostUserLogout event hook
|
||||
await Apps.self?.triggerEvent(AppEvents.IPostUserLoggedOut, user);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { refreshLoginServices } from '../../lib/refreshLoginServices';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
refreshOAuthService(): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async refreshOAuthService() {
|
||||
methodDeprecationLogger.method('refreshOAuthService', '9.0.0', '/v1/settings.refreshOAuthServices');
|
||||
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'refreshOAuthService',
|
||||
});
|
||||
}
|
||||
|
||||
if ((await hasPermissionAsync(userId, 'add-oauth-service')) !== true) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Refresh OAuth Services is not allowed', {
|
||||
method: 'refreshOAuthService',
|
||||
action: 'Refreshing_OAuth_Services',
|
||||
});
|
||||
}
|
||||
|
||||
await refreshLoginServices();
|
||||
},
|
||||
});
|
||||
19
apps/meteor/server/meteor-methods/auth/regenerateCodes.ts
Normal file
19
apps/meteor/server/meteor-methods/auth/regenerateCodes.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { regenerateTotpCodes } from '../../lib/2fa/functions/totp';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'2fa:regenerateCodes': (userToken: string) => { codes: string[] } | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async '2fa:regenerateCodes'(userToken) {
|
||||
methodDeprecationLogger.method('2fa:regenerateCodes', '9.0.0', '/v1/users.regenerateTotpCodes');
|
||||
return regenerateTotpCodes(Meteor.userId(), userToken);
|
||||
},
|
||||
});
|
||||
86
apps/meteor/server/meteor-methods/auth/removeOAuthService.ts
Normal file
86
apps/meteor/server/meteor-methods/auth/removeOAuthService.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Settings } from '@rocket.chat/models';
|
||||
import { capitalize } from '@rocket.chat/tools';
|
||||
import { check } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { notifyOnSettingChangedById } from '../../lib/notifyListener';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
removeOAuthService(name: string): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
export const removeCustomOAuthSettings = async (name: string): Promise<void> => {
|
||||
const normalized = capitalize(name.toLowerCase().replace(/[^a-z0-9_]/g, ''));
|
||||
|
||||
if (!normalized) {
|
||||
throw new Meteor.Error('error-invalid-name', 'Invalid OAuth service name', { method: 'removeOAuthService' });
|
||||
}
|
||||
|
||||
const settingsIds = [
|
||||
`Accounts_OAuth_Custom-${normalized}`,
|
||||
`Accounts_OAuth_Custom-${normalized}-url`,
|
||||
`Accounts_OAuth_Custom-${normalized}-token_path`,
|
||||
`Accounts_OAuth_Custom-${normalized}-identity_path`,
|
||||
`Accounts_OAuth_Custom-${normalized}-authorize_path`,
|
||||
`Accounts_OAuth_Custom-${normalized}-scope`,
|
||||
`Accounts_OAuth_Custom-${normalized}-access_token_param`,
|
||||
`Accounts_OAuth_Custom-${normalized}-token_sent_via`,
|
||||
`Accounts_OAuth_Custom-${normalized}-identity_token_sent_via`,
|
||||
`Accounts_OAuth_Custom-${normalized}-id`,
|
||||
`Accounts_OAuth_Custom-${normalized}-secret`,
|
||||
`Accounts_OAuth_Custom-${normalized}-button_label_text`,
|
||||
`Accounts_OAuth_Custom-${normalized}-button_label_color`,
|
||||
`Accounts_OAuth_Custom-${normalized}-button_color`,
|
||||
`Accounts_OAuth_Custom-${normalized}-login_style`,
|
||||
`Accounts_OAuth_Custom-${normalized}-key_field`,
|
||||
`Accounts_OAuth_Custom-${normalized}-username_field`,
|
||||
`Accounts_OAuth_Custom-${normalized}-email_field`,
|
||||
`Accounts_OAuth_Custom-${normalized}-name_field`,
|
||||
`Accounts_OAuth_Custom-${normalized}-avatar_field`,
|
||||
`Accounts_OAuth_Custom-${normalized}-roles_claim`,
|
||||
`Accounts_OAuth_Custom-${normalized}-merge_roles`,
|
||||
`Accounts_OAuth_Custom-${normalized}-roles_to_sync`,
|
||||
`Accounts_OAuth_Custom-${normalized}-merge_users`,
|
||||
`Accounts_OAuth_Custom-${normalized}-show_button`,
|
||||
`Accounts_OAuth_Custom-${normalized}-groups_claim`,
|
||||
`Accounts_OAuth_Custom-${normalized}-channels_admin`,
|
||||
`Accounts_OAuth_Custom-${normalized}-map_channels`,
|
||||
`Accounts_OAuth_Custom-${normalized}-groups_channel_map`,
|
||||
`Accounts_OAuth_Custom-${normalized}-merge_users_distinct_services`,
|
||||
];
|
||||
|
||||
const promises = settingsIds.map((id) => Settings.removeById(id));
|
||||
|
||||
(await Promise.all(promises)).forEach((value, index) => {
|
||||
if (value?.deletedCount) {
|
||||
void notifyOnSettingChangedById(settingsIds[index], 'removed');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async removeOAuthService(name) {
|
||||
methodDeprecationLogger.method('removeOAuthService', '9.0.0', '/v1/settings.removeCustomOAuth');
|
||||
check(name, String);
|
||||
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'removeOAuthService',
|
||||
});
|
||||
}
|
||||
|
||||
if ((await hasPermissionAsync(userId, 'add-oauth-service')) !== true) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'removeOAuthService' });
|
||||
}
|
||||
|
||||
await removeCustomOAuthSettings(name);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { removeRoleFromPermissionMethod } from '../../lib/authorization/permissionRole';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'authorization:removeRoleFromPermission'(permissionId: string, role: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async 'authorization:removeRoleFromPermission'(permissionId, role) {
|
||||
methodDeprecationLogger.method('authorization:removeRoleFromPermission', '9.0.0', '/v1/permissions.removeRole');
|
||||
const uid = Meteor.userId();
|
||||
if (!uid) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'authorization:removeRoleFromPermission' });
|
||||
}
|
||||
await removeRoleFromPermissionMethod(uid, permissionId, role);
|
||||
},
|
||||
});
|
||||
77
apps/meteor/server/meteor-methods/auth/removeUserFromRole.ts
Normal file
77
apps/meteor/server/meteor-methods/auth/removeUserFromRole.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { api } from '@rocket.chat/core-services';
|
||||
import type { IRole, IUser } from '@rocket.chat/core-typings';
|
||||
import { Roles, Users } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { removeUserFromRolesAsync } from '../../lib/roles/removeUserFromRoles';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
export const removeUserFromRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
|
||||
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
|
||||
method: 'authorization:removeUserFromRole',
|
||||
action: 'Accessing_permissions',
|
||||
});
|
||||
}
|
||||
|
||||
if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
|
||||
method: 'authorization:removeUserFromRole',
|
||||
});
|
||||
}
|
||||
|
||||
const role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
|
||||
if (!role) {
|
||||
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
|
||||
method: 'authorization:removeUserFromRole',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await Users.findOneByUsernameIgnoringCase(username, {
|
||||
projection: {
|
||||
_id: 1,
|
||||
roles: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?._id) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'authorization:removeUserFromRole',
|
||||
});
|
||||
}
|
||||
|
||||
// prevent removing last user from admin role
|
||||
if (role._id === 'admin') {
|
||||
const adminCount = await Users.countDocuments({
|
||||
roles: {
|
||||
$in: ['admin'],
|
||||
},
|
||||
});
|
||||
|
||||
const userIsAdmin = user.roles?.indexOf('admin') > -1;
|
||||
if (adminCount === 1 && userIsAdmin) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', {
|
||||
method: 'removeUserFromRole',
|
||||
action: 'Remove_last_admin',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const remove = await removeUserFromRolesAsync(user._id, [role._id], scope);
|
||||
const event = {
|
||||
type: 'removed',
|
||||
_id: role._id,
|
||||
u: {
|
||||
_id: user._id,
|
||||
username,
|
||||
},
|
||||
scope,
|
||||
} as const;
|
||||
if (settings.get('UI_DisplayRoles')) {
|
||||
void api.broadcast('user.roleUpdate', event);
|
||||
}
|
||||
void api.broadcast('federation.userRoleChanged', { ...event, givenByUserId: userId });
|
||||
|
||||
return remove;
|
||||
};
|
||||
78
apps/meteor/server/meteor-methods/auth/samlLogout.ts
Normal file
78
apps/meteor/server/meteor-methods/auth/samlLogout.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Users } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import type { IServiceProviderOptions } from '../../lib/saml/definition/IServiceProviderOptions';
|
||||
import { SAMLServiceProvider } from '../../lib/saml/lib/ServiceProvider';
|
||||
import { SAMLUtils } from '../../lib/saml/lib/Utils';
|
||||
|
||||
/**
|
||||
* Fetch SAML provider configs for given 'provider'.
|
||||
*/
|
||||
function getSamlServiceProviderOptions(provider: string): IServiceProviderOptions {
|
||||
if (!provider) {
|
||||
throw new Meteor.Error('no-saml-provider', 'SAML internal error', {
|
||||
method: 'getSamlServiceProviderOptions',
|
||||
});
|
||||
}
|
||||
|
||||
const providers = SAMLUtils.serviceProviders;
|
||||
|
||||
const samlProvider = function (element: IServiceProviderOptions): boolean {
|
||||
return element.provider === provider;
|
||||
};
|
||||
|
||||
return providers.filter(samlProvider)[0];
|
||||
}
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
samlLogout(provider: string): string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async samlLogout(provider: string) {
|
||||
const userId = Meteor.userId();
|
||||
// Make sure the user is logged in before we initiate SAML Logout
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'samlLogout' });
|
||||
}
|
||||
const providerConfig = getSamlServiceProviderOptions(provider);
|
||||
|
||||
SAMLUtils.log({ msg: 'Logout request', providerConfig });
|
||||
// This query should respect upcoming array of SAML logins
|
||||
const user = await Users.getSAMLByIdAndSAMLProvider(userId, provider);
|
||||
if (!user?.services?.saml) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { nameID, idpSession } = user.services.saml;
|
||||
SAMLUtils.log({ msg: `NameID for user ${Meteor.userId()} found`, nameID });
|
||||
|
||||
const _saml = new SAMLServiceProvider(providerConfig);
|
||||
|
||||
if (!nameID || !idpSession) {
|
||||
SAMLUtils.log({ msg: 'No NameID or idpSession found for user', userId });
|
||||
return;
|
||||
}
|
||||
|
||||
const request = _saml.generateLogoutRequest({
|
||||
nameID: nameID || idpSession,
|
||||
sessionIndex: idpSession,
|
||||
});
|
||||
|
||||
SAMLUtils.log({ request, msg: '----Logout Request---' });
|
||||
|
||||
// request.request: actual XML SAML Request
|
||||
// request.id: comminucation id which will be mentioned in the ResponseTo field of SAMLResponse
|
||||
|
||||
await Users.setSamlInResponseTo(userId, request.id);
|
||||
|
||||
const result = await _saml.requestToUrl(request.request, 'logout');
|
||||
SAMLUtils.log({ msg: 'SAML Logout Request URL generated', result });
|
||||
|
||||
return result;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Users } from '@rocket.chat/models';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { check } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export const sendConfirmationEmail = async (to: string): Promise<boolean> => {
|
||||
check(to, String);
|
||||
|
||||
const email = to.trim();
|
||||
|
||||
const user = await Users.findOneByEmailAddress(email, { projection: { emails: 1 } });
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not re-send a verification mail to an address that is already verified.
|
||||
const target = user.emails?.find((e) => e.address.toLowerCase() === email.toLowerCase() && !e.verified);
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Accounts.sendVerificationEmail(user._id, target.address);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${error.message}`, {
|
||||
method: 'registerUser',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Users } from '@rocket.chat/models';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { check } from 'meteor/check';
|
||||
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { SystemLogger } from '../../lib/logger/system';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
sendForgotPasswordEmail(to: string): boolean | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const sendForgotPasswordEmail = async (to: string): Promise<boolean | undefined> => {
|
||||
const email = to.trim().toLowerCase();
|
||||
|
||||
const user = await Users.findOneByEmailAddress(email, { projection: { _id: 1, services: 1 } });
|
||||
|
||||
if (!user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user.services && !user.services.password) {
|
||||
if (!settings.get('Accounts_AllowPasswordChangeForOAuthUsers')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Accounts.sendResetPasswordEmail(user._id, email);
|
||||
return true;
|
||||
} catch (err) {
|
||||
SystemLogger.error({ err });
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async sendForgotPasswordEmail(to) {
|
||||
check(to, String);
|
||||
|
||||
return sendForgotPasswordEmail(to);
|
||||
},
|
||||
});
|
||||
|
||||
DDPRateLimiter.addRule(
|
||||
{
|
||||
type: 'method',
|
||||
name: 'sendForgotPasswordEmail',
|
||||
clientAddress() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
10,
|
||||
60000,
|
||||
);
|
||||
82
apps/meteor/server/meteor-methods/auth/updateOAuthApp.ts
Normal file
82
apps/meteor/server/meteor-methods/auth/updateOAuthApp.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { IOAuthApps } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { OAuthApps, Users } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { parseUriList } from '../../lib/auth/oauth2-server/parseUriList';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
updateOAuthApp(
|
||||
applicationId: IOAuthApps['_id'],
|
||||
application: Pick<IOAuthApps, 'name' | 'redirectUri' | 'active'>,
|
||||
): Promise<IOAuthApps | null>;
|
||||
}
|
||||
}
|
||||
|
||||
export const updateOAuthApp = async (
|
||||
userId: string,
|
||||
applicationId: IOAuthApps['_id'],
|
||||
application: Pick<IOAuthApps, 'name' | 'redirectUri' | 'active'>,
|
||||
): Promise<IOAuthApps | null> => {
|
||||
if (!(await hasPermissionAsync(userId, 'manage-oauth-apps'))) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'updateOAuthApp' });
|
||||
}
|
||||
|
||||
if (!application.name || typeof application.name.valueOf() !== 'string' || application.name.trim() === '') {
|
||||
throw new Meteor.Error('error-invalid-name', 'Invalid name', { method: 'updateOAuthApp' });
|
||||
}
|
||||
|
||||
if (!application.redirectUri || typeof application.redirectUri.valueOf() !== 'string' || application.redirectUri.trim() === '') {
|
||||
throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', {
|
||||
method: 'updateOAuthApp',
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof application.active !== 'boolean') {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
|
||||
method: 'updateOAuthApp',
|
||||
});
|
||||
}
|
||||
|
||||
const redirectUri = parseUriList(application.redirectUri);
|
||||
|
||||
if (redirectUri.length === 0) {
|
||||
throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', {
|
||||
method: 'updateOAuthApp',
|
||||
});
|
||||
}
|
||||
|
||||
const updatedApplication = await OAuthApps.updateById(applicationId, {
|
||||
name: application.name,
|
||||
active: application.active,
|
||||
redirectUri,
|
||||
_updatedBy: await Users.findOneById(userId, {
|
||||
projection: {
|
||||
username: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (updatedApplication == null) {
|
||||
throw new Meteor.Error('error-application-not-found', 'Application not found', {
|
||||
method: 'updateOAuthApp',
|
||||
});
|
||||
}
|
||||
|
||||
return updatedApplication;
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async updateOAuthApp(applicationId, application) {
|
||||
methodDeprecationLogger.method('updateOAuthApp', '9.0.0', '/v1/oauth-apps.update');
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateOAuthApp' });
|
||||
}
|
||||
|
||||
return updateOAuthApp(this.userId, applicationId, application);
|
||||
},
|
||||
});
|
||||
20
apps/meteor/server/meteor-methods/auth/validateTempToken.ts
Normal file
20
apps/meteor/server/meteor-methods/auth/validateTempToken.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { validateTotpTempToken } from '../../lib/2fa/functions/totp';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
'2fa:validateTempToken': (userToken: string) => { codes: string[] } | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async '2fa:validateTempToken'(userToken) {
|
||||
methodDeprecationLogger.method('2fa:validateTempToken', '9.0.0', '/v1/users.validateTotp');
|
||||
const { 'x-auth-token': xAuthToken } = this.connection?.httpHeaders ?? {};
|
||||
return validateTotpTempToken(Meteor.userId(), userToken, xAuthToken);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user