import { License } from '@rocket.chat/license'; import { Users } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; import { userLoggedOut } from './userLoggedOut'; import { settings } from '../../settings'; import { SystemLogger } from '../logger/system'; export async function userLogout(userId: string): Promise { const { workspaceRegistered } = await retrieveRegistrationStatus(); if (!workspaceRegistered) { return ''; } if (!userId) { return ''; } // Offline (air-gapped) licenses forbid the outbound token-revocation call, but // the local cloud credentials must still be destroyed on logout. if (License.hasOfflineLicense()) { return userLoggedOut(userId); } const user = await Users.findOneById(userId); if (user?.services?.cloud?.refreshToken) { try { const clientId = settings.get('Cloud_Workspace_Client_Id'); if (!clientId) { return ''; } const clientSecret = settings.get('Cloud_Workspace_Client_Secret'); const { refreshToken } = user.services.cloud; const cloudUrl = settings.get('Cloud_Url'); await fetch(`${cloudUrl}/api/oauth/revoke`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, params: { client_id: clientId, client_secret: clientSecret, token: refreshToken, token_type_hint: 'refresh_token', }, // SECURITY: the URL is a default hardcoded value or an envvar/setting set by an admin. It's safe to disable this check. ignoreSsrfValidation: true, }); } catch (err) { SystemLogger.error({ msg: 'Failed to get Revoke refresh token to logout of Rocket.Chat Cloud', url: '/api/oauth/revoke', err, }); } } return userLoggedOut(userId); }