From 30b2e63cf5578d8cce3391f3c3699342fed99064 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 16 Sep 2023 11:12:51 -0300 Subject: [PATCH] Create APIs and add info to reports --- apps/meteor/app/api/server/v1/misc.ts | 76 ++++++++++++++++++- .../server/functions/buildRegistrationData.ts | 4 + .../app/statistics/server/lib/statistics.ts | 3 + apps/meteor/client/startup/rootUrlChange.ts | 11 +-- apps/meteor/server/models/raw/Settings.ts | 19 +++++ apps/meteor/server/settings/misc.ts | 40 ++-------- packages/core-typings/src/ISetting.ts | 2 +- packages/core-typings/src/IStats.ts | 2 + .../src/models/ISettingsModel.ts | 5 ++ packages/rest-typings/src/v1/misc.ts | 22 ++++++ 10 files changed, 145 insertions(+), 39 deletions(-) diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index dec4da6bf87b0..ae5a79719cce3 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -1,13 +1,14 @@ import crypto from 'crypto'; import type { IUser } from '@rocket.chat/core-typings'; -import { Users } from '@rocket.chat/models'; +import { Settings, Users } from '@rocket.chat/models'; import { isShieldSvgProps, isSpotlightProps, isDirectoryProps, isMethodCallProps, isMethodCallAnonProps, + isFingerprintProps, isMeteorCall, validateParamsPwGetPolicyRest, } from '@rocket.chat/rest-typings'; @@ -16,6 +17,7 @@ import EJSON from 'ejson'; import { check } from 'meteor/check'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; +import { v4 as uuidv4 } from 'uuid'; import { i18n } from '../../../../server/lib/i18n'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -643,3 +645,75 @@ API.v1.addRoute( }, }, ); + +/** + * @openapi + * /api/v1/fingerprint: + * post: + * description: Update Fingerprint definition as a new workspace or update of configuration + * security: + * $ref: '#/security/authenticated' + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * setDeploymentAs: + * type: string + * example: | + * { + * "setDeploymentAs": "new-workspace" + * } + * responses: + * 200: + * description: Workspace successfully configured + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiSuccessV1' + * default: + * description: Unexpected error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiFailureV1' + */ +API.v1.addRoute( + 'fingerprint', + { + authRequired: true, + validateParams: isFingerprintProps, + }, + { + async post() { + check(this.bodyParams, { + setDeploymentAs: String, + }); + + if (this.bodyParams.setDeploymentAs === 'new-workspace') { + await Promise.all([ + Settings.resetValueById('uniqueID', process.env.DEPLOYMENT_ID || uuidv4()), + // Settings.resetValueById('Cloud_Url'), + Settings.resetValueById('Cloud_Service_Agree_PrivacyTerms'), + Settings.resetValueById('Cloud_Workspace_Id'), + Settings.resetValueById('Cloud_Workspace_Name'), + Settings.resetValueById('Cloud_Workspace_Client_Id'), + Settings.resetValueById('Cloud_Workspace_Client_Secret'), + Settings.resetValueById('Cloud_Workspace_Client_Secret_Expires_At'), + Settings.resetValueById('Cloud_Workspace_Registration_Client_Uri'), + Settings.resetValueById('Cloud_Workspace_PublicKey'), + Settings.resetValueById('Cloud_Workspace_License'), + Settings.resetValueById('Cloud_Workspace_Had_Trial'), + Settings.resetValueById('Cloud_Workspace_Access_Token'), + Settings.resetValueById('Cloud_Workspace_Access_Token_Expires_At', new Date(0)), + Settings.resetValueById('Cloud_Workspace_Registration_State'), + ]); + } + + await Settings.updateValueById('Deployment_FingerPrint_Verified', true); + + return API.v1.success({}); + }, + }, +); diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts index d65897b720945..981a516a46195 100644 --- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts @@ -45,6 +45,8 @@ export async function buildWorkspaceRegistrationData { const updateWorkspace = (): void => { imperativeModal.close(); - console.log('updateWorkspace'); + void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'updated-configuration' }).then(() => { + dispatchToastMessage({ type: 'success', message: t('Saved') }); + }); }; const setNewWorkspace = (): void => { imperativeModal.close(); - console.log('setNewWorkspace'); - // void sdk.call('saveSetting', 'Site_Url', currentUrl).then(() => { - // dispatchToastMessage({ type: 'success', message: t('Saved') }); - // }); + void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'new-workspace' }).then(() => { + dispatchToastMessage({ type: 'success', message: t('Saved') }); + }); }; const openModal = (): void => { diff --git a/apps/meteor/server/models/raw/Settings.ts b/apps/meteor/server/models/raw/Settings.ts index 3a5d150c01585..1154b7dfe630c 100644 --- a/apps/meteor/server/models/raw/Settings.ts +++ b/apps/meteor/server/models/raw/Settings.ts @@ -69,6 +69,25 @@ export class SettingsRaw extends BaseRaw implements ISettingsModel { return this.updateOne(query, update); } + async resetValueById( + _id: string, + value?: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, + ): Promise { + if (value == null) { + const record = await this.findOneById(_id); + if (record) { + const prop = record.valueSource || 'packageValue'; + value = record[prop]; + } + } + + if (value == null) { + return; + } + + return this.updateValueById(_id, value); + } + async incrementValueById(_id: ISetting['_id'], value = 1): Promise { return this.updateOne( { diff --git a/apps/meteor/server/settings/misc.ts b/apps/meteor/server/settings/misc.ts index 96844e65648f3..96aa6728accf7 100644 --- a/apps/meteor/server/settings/misc.ts +++ b/apps/meteor/server/settings/misc.ts @@ -7,16 +7,11 @@ import { v4 as uuidv4 } from 'uuid'; import { settingsRegistry, settings } from '../../app/settings/server'; -interface Fingerprint { - site_url: string; - db_connection_string: string; -} - // TODO: // Apis to set as new deploy or update // On update list variables to re generate / cleanup -// Modal to request action -// Process on site_url change +// Send alongside statistics and cloud sync +// Texts/translations const logger = new Logger('FingerPrint'); @@ -25,28 +20,13 @@ const generateFingerprint = function () { const dbConnectionString = process.env.MONGO_URL; const fingerprint = `${siteUrl}${dbConnectionString}`; - console.log(fingerprint); return crypto.createHash('sha256').update(fingerprint).digest('base64'); }; const updateFingerprint = async function (fingerprint: string, verified: boolean) { - await Settings.updateOne( - { _id: 'Deployment_FingerPrint_Hash' }, - { - $set: { - value: fingerprint, - }, - }, - ); - - await Settings.updateOne( - { _id: 'Deployment_FingerPrint_Verified' }, - { - $set: { - value: verified, - }, - }, - ); + await Settings.updateValueById('Deployment_FingerPrint_Hash', fingerprint); + + await Settings.updateValueById('Deployment_FingerPrint_Verified', verified); }; const verifyFingerPrint = async function () { @@ -54,9 +34,8 @@ const verifyFingerPrint = async function () { const fingerprint = generateFingerprint(); - console.log(DeploymentFingerPrintRecordHash); if (!DeploymentFingerPrintRecordHash) { - logger.info('Generating fingerprint for the first time'); + logger.info('Generating fingerprint for the first time', fingerprint); await updateFingerprint(fingerprint, true); return; } @@ -86,16 +65,13 @@ export const createMiscSettings = async () => { await settingsRegistry.add('Deployment_FingerPrint_Hash', '', { public: false, - blocked: true, - // hidden: true, - // secret: true, + readonly: true, }); await settingsRegistry.add('Deployment_FingerPrint_Verified', false, { type: 'boolean', public: true, - blocked: true, - // hidden: true, + readonly: true, }); await verifyFingerPrint(); diff --git a/packages/core-typings/src/ISetting.ts b/packages/core-typings/src/ISetting.ts index 0766d782980c3..7b3aaa5cf2a48 100644 --- a/packages/core-typings/src/ISetting.ts +++ b/packages/core-typings/src/ISetting.ts @@ -72,7 +72,7 @@ export interface ISettingBase { hidden?: boolean; modules?: Array; invalidValue?: SettingValue; - valueSource?: string; + valueSource?: 'packageValue' | 'processEnvValue'; secret?: boolean; i18nDescription?: string; autocomplete?: boolean; diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 2ea8115a727c9..3c6428a0b0a74 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -8,6 +8,8 @@ export interface IStats { _id: string; wizard: Record; uniqueId: string; + deploymentFingerprintHash: string; + deploymentFingerprintVerified: boolean; installedAt?: string; version?: string; tag?: string; diff --git a/packages/model-typings/src/models/ISettingsModel.ts b/packages/model-typings/src/models/ISettingsModel.ts index 9dc2005867fa6..d382d4853a4b7 100644 --- a/packages/model-typings/src/models/ISettingsModel.ts +++ b/packages/model-typings/src/models/ISettingsModel.ts @@ -17,6 +17,11 @@ export interface ISettingsModel extends IBaseModel { value: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, ): Promise; + resetValueById( + _id: string, + value?: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, + ): Promise; + incrementValueById(_id: ISetting['_id'], value?: number): Promise; updateOptionsById( diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index 4af37334e287d..804b72a763de1 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -164,6 +164,22 @@ const MethodCallAnonSchema = { export const isMethodCallAnonProps = ajv.compile(MethodCallAnonSchema); +type Fingerprint = { setDeploymentAs: 'new-workspace' | 'updated-configuration' }; + +const FingerprintSchema = { + type: 'object', + properties: { + setDeploymentAs: { + type: 'string', + enum: ['new-workspace', 'updated-configuration'], + }, + }, + required: ['setDeploymentAs'], + additionalProperties: false, +}; + +export const isFingerprintProps = ajv.compile(FingerprintSchema); + type PwGetPolicyReset = { token: string }; const PwGetPolicyResetSchema = { @@ -229,6 +245,12 @@ export type MiscEndpoints = { }; }; + '/v1/fingerprint': { + POST: (params: Fingerprint) => { + success: boolean; + }; + }; + '/v1/smtp.check': { GET: () => { isSMTPConfigured: boolean;