Billing - Settings - after sync with Serge
MODIFY
src/server/rest/v1/service/BillingSettingService.ts
src/server/rest/v1/service/BillingSettingService.ts
import { Action, Entity } from '../../../../types/Authorization';
-import { BillingSettings, SettingDB } from '../../../../types/Setting';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
import AppAuthError from '../../../../exception/AppAuthError';
import AppError from '../../../../exception/AppError';
import Authorizations from '../../../../authorization/Authorizations';
+import BackendError from '../../../../exception/BackendError';
import BillingSettingStorage from '../../../../storage/mongodb/BillingSettingStorage';
+import { BillingSettings } from '../../../../types/Setting';
import Constants from '../../../../utils/Constants';
import Cypher from '../../../../utils/Cypher';
import { ServerAction } from '../../../../types/Server';
-import SettingSecurity from './security/SettingSecurity';
import { StatusCodes } from 'http-status-codes';
import UtilsService from './UtilsService';
import _ from 'lodash';
@@ -28,9 +28,10 @@ export default class BillingSettingService {
module: MODULE_NAME, method: 'handleGetBillingSetting',
});
}
- const billingSettings: BillingSettings = await BillingSettingStorage.getBillingSettings(req.user.tenantID, true);
- if (billingSettings) {
- res.json(billingSettings);
+ const allBillingSettings: BillingSettings[] = await BillingSettingStorage.getBillingSettings(req.user.tenantID);
+ if (allBillingSettings) {
+ allBillingSettings.map((billingSettings) => BillingSettingService.hashSensitiveData(req.user.tenantID, billingSettings));
+ res.json(allBillingSettings);
} else {
res.sendStatus(StatusCodes.NOT_FOUND);
}
@@ -47,8 +48,11 @@ export default class BillingSettingService {
});
}
const settingID = req.params.id;
- const billingSettings: BillingSettings = await BillingSettingStorage.getBillingSetting(req.user.tenantID, settingID, true);
+ const billingSettings: BillingSettings = await BillingSettingStorage.getBillingSetting(req.user.tenantID, settingID);
if (billingSettings) {
+ _.set(billingSettings, 'sk-enc', billingSettings.stripe.secretKey);
+ _.set(billingSettings, 'sk-ori', await Cypher.decrypt(req.user.tenantID, billingSettings.stripe.secretKey));
+ BillingSettingService.hashSensitiveData(req.user.tenantID, billingSettings);
res.json(billingSettings);
} else {
res.sendStatus(StatusCodes.NOT_FOUND);
@@ -58,11 +62,9 @@ export default class BillingSettingService {
public static async handleUpdateBillingSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- // TODO - should only accept BillingSettings as input
- // const newSettings = SettingSecurity.filterBillingSettingUpdateRequest(req);
// TODO - sanitize it
- const settingID = req.params.id;
- const newBillingSettings = req.body;
+ const newBillingSettings = req.body as BillingSettings;
+ newBillingSettings.id = req.params.id;
UtilsService.assertIdIsProvided(action, newBillingSettings.id, MODULE_NAME, 'handleUpdateSetting', req.user);
if (!await Authorizations.canUpdateSetting(req.user)) {
throw new AppAuthError({
@@ -72,11 +74,19 @@ export default class BillingSettingService {
module: MODULE_NAME, method: 'handleUpdateBillingSetting',
});
}
+ // Load previous settings
+ const billingSettings = await BillingSettingStorage.getBillingSetting(req.user.tenantID, newBillingSettings.id);
+ await BillingSettingService.alterSensitiveData(req.user.tenantID, billingSettings, newBillingSettings);
+ // Now populates the settings with the new values
+ billingSettings.id = newBillingSettings.id;
+ billingSettings.type = newBillingSettings.type;
+ billingSettings.billing = newBillingSettings.billing;
+ billingSettings.stripe = newBillingSettings.stripe;
// Update timestamp
- newBillingSettings.lastChangedBy = { 'id': req.user.id };
- newBillingSettings.lastChangedOn = new Date();
+ billingSettings.lastChangedBy = { 'id': req.user.id };
+ billingSettings.lastChangedOn = new Date();
// Let's save it
- const id = await BillingSettingStorage.saveBillingSetting(req.user.tenantID, settingID, newBillingSettings as BillingSettings, true);
+ const id = await BillingSettingStorage.saveBillingSetting(req.user.tenantID, billingSettings);
if (!id) {
res.sendStatus(StatusCodes.NOT_FOUND);
} else {
@@ -96,4 +106,79 @@ export default class BillingSettingService {
res.sendStatus(StatusCodes.NOT_IMPLEMENTED);
next();
}
+
+ private static async alterSensitiveData(tenantID: string, billingSettings: BillingSettings, newBillingSettings: BillingSettings) {
+ // Process the sensitive data if any
+ // Preprocess the data to take care of updated values
+ if (billingSettings.sensitiveData) {
+ if (!Array.isArray(billingSettings.sensitiveData)) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.CYPHER_INVALID_SENSITIVE_DATA_ERROR,
+ message: `The property 'sensitiveData' for Setting with ID '${newBillingSettings.id}' is not an array`,
+ module: MODULE_NAME,
+ method: 'processSensitiveData',
+ });
+ }
+ // Process sensitive properties
+ for (const propertyName of billingSettings.sensitiveData) {
+ // TODO - find a better way - HACK to be backward compatible with SettingDB
+ const normalizedPropertyName = propertyName.replace('content.', '');
+ // Get the sensitive property from the request
+ const valueInRequest = _.get(newBillingSettings, normalizedPropertyName);
+ if (valueInRequest && valueInRequest.length > 0) {
+ // Get the sensitive property from the DB
+ const valueInDb = _.get(billingSettings, normalizedPropertyName);
+ if (valueInDb && valueInDb.length > 0) {
+ const hashedValueInDB = Cypher.hash(valueInDb);
+ if (valueInRequest !== hashedValueInDB) {
+ // Yes: Encrypt
+ _.set(newBillingSettings, normalizedPropertyName, await Cypher.encrypt(tenantID, valueInRequest));
+ } else {
+ // No: Put back the encrypted value
+ _.set(newBillingSettings, normalizedPropertyName, valueInDb);
+ }
+ } else {
+ // Value in db is empty then encrypt
+ _.set(newBillingSettings, normalizedPropertyName, await Cypher.encrypt(tenantID, valueInRequest));
+ }
+ } else {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.CYPHER_INVALID_SENSITIVE_DATA_ERROR,
+ message: `The property '${propertyName}' for Setting with ID '${newBillingSettings.id}' is not set`,
+ module: MODULE_NAME,
+ method: 'processSensitiveData',
+ });
+ }
+ }
+ }
+ }
+
+ private static hashSensitiveData(tenantID: string, billingSettings: BillingSettings): BillingSettings {
+ if (billingSettings.sensitiveData) {
+ // Check that sensitive data is an array
+ if (!Array.isArray(billingSettings.sensitiveData)) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'hashSensitiveDataInJSON',
+ message: 'The property \'sensitiveData\' is not an array'
+ });
+ }
+ for (const propertyName of billingSettings.sensitiveData) {
+ // TODO - find a better way - HACK to be backward compatible with SettingDB
+ const normalizedPropertyName = propertyName.replace('content.', '');
+ // Check that the property does exist otherwise skip to the next property
+ if (_.has(billingSettings, normalizedPropertyName)) {
+ const value = _.get(billingSettings, normalizedPropertyName);
+ // If the value is undefined, null or empty then do nothing and skip to the next property
+ if (value && value.length > 0) {
+ _.set(billingSettings, normalizedPropertyName, Cypher.hash(value));
+ }
+ }
+ }
+ }
+ return billingSettings;
+ }
}

MODIFY
src/storage/mongodb/BillingSettingStorage.ts
src/storage/mongodb/BillingSettingStorage.ts
import { BillingSettings, BillingSettingsType, SettingDB } from '../../types/Setting';
-import AppError from '../../exception/AppError';
import Constants from '../../utils/Constants';
-import Cypher from '../../utils/Cypher';
-import { HTTPError } from '../../types/HTTPError';
import SettingStorage from './SettingStorage';
import TenantComponents from '../../types/TenantComponents';
-import _ from 'lodash';
const MODULE_NAME = 'BillingSettingStorage';
-export default class BillingSettingStorage {
- public static alterBillingSettings(billingSettings: BillingSettings, setting: SettingDB, encryptSensitiveData: boolean): BillingSettings {
- // Encrypt Sensitive Data
- if (encryptSensitiveData) {
- Cypher.hashSensitiveDataInJSON(setting);
- }
- // Extract the data
- const content = setting.content;
- billingSettings.id = setting.id;
- billingSettings.sensitiveData = setting.sensitiveData;
- billingSettings.backupSensitiveData = setting.backupSensitiveData;
- // Billing Common Properties
- if (content.billing) {
- const { isTransactionBillingActivated, immediateBillingAllowed, periodicBillingAllowed, taxID, usersLastSynchronizedOn } = content.billing;
- billingSettings.billing = {
- isTransactionBillingActivated,
- immediateBillingAllowed,
- periodicBillingAllowed,
- usersLastSynchronizedOn,
- taxID,
- };
- }
- // Billing Concrete Implementation Properties
- if (content.stripe) {
- billingSettings.type = BillingSettingsType.STRIPE;
- const { url, publicKey, secretKey } = content.stripe;
- billingSettings.stripe = {
- url,
- publicKey,
- secretKey,
- };
- }
- return billingSettings;
- }
+export default class BillingSettingStorage {
- public static async getBillingSettings(tenantID: string, encryptSensitiveData = false): Promise<BillingSettings> {
+ public static async getBillingSettings(tenantID: string): Promise<BillingSettings[]> {
const settings = await SettingStorage.getSettings(tenantID, { identifier: TenantComponents.BILLING }, Constants.DB_PARAMS_SINGLE_RECORD);
- if (settings?.result?.[0]?.content) {
- const billingSettings = {
- identifier: TenantComponents.BILLING,
- } as BillingSettings;
- BillingSettingStorage.alterBillingSettings(billingSettings, settings.result[0], encryptSensitiveData);
- return billingSettings;
- }
- return null;
- }
-
- public static async getBillingSetting(tenantID: string, settingID: string, encryptSensitiveData = false): Promise<BillingSettings> {
- const settings = await SettingStorage.getSettings(tenantID, { settingID }, Constants.DB_PARAMS_SINGLE_RECORD);
- if (settings?.result?.[0]?.content) {
- const billingSettings = {
- identifier: TenantComponents.BILLING,
- } as BillingSettings;
- BillingSettingStorage.alterBillingSettings(billingSettings, settings.result[0], encryptSensitiveData);
- return billingSettings;
+ if (settings && settings.count > 0) {
+ const allBillingSettings: BillingSettings[] = settings.result.map((setting) => BillingSettingStorage.convertToBillingSettings(setting));
+ return allBillingSettings;
}
return null;
}
- public static async saveBillingSetting(tenantID: string, settingID: string, newBillingSettings: Partial<BillingSettings>, encryptSensitiveData = false): Promise<string> {
- // Load previous settings
+ public static async getBillingSetting(tenantID: string, settingID: string): Promise<BillingSettings> {
const settings = await SettingStorage.getSettings(tenantID, { settingID }, Constants.DB_PARAMS_SINGLE_RECORD);
if (settings?.result?.[0]?.content) {
- // Build internal structure
- const settingsToSave = settings.result[0];
- if (encryptSensitiveData) {
- await BillingSettingStorage.processSensitiveData(tenantID, settingsToSave, newBillingSettings);
- }
- const { isTransactionBillingActivated, usersLastSynchronizedOn } = settingsToSave.content.billing;
- const { immediateBillingAllowed, periodicBillingAllowed, taxID } = newBillingSettings.billing;
- // Common Properties
- settingsToSave.content.billing = {
- isTransactionBillingActivated,usersLastSynchronizedOn,
- immediateBillingAllowed, periodicBillingAllowed, taxID
- };
- // STRIPE Properties
- if (settingsToSave.content.type === BillingSettingsType.STRIPE) {
- settingsToSave.content.stripe = newBillingSettings.stripe;
- }
- // Save
- return SettingStorage.saveSettings(tenantID, settingsToSave);
+ const setting: SettingDB = settings.result[0];
+ return BillingSettingStorage.convertToBillingSettings(setting);
}
return null;
}
- private static async processSensitiveData(tenantID: string, setting: SettingDB, billingSettings: Partial<BillingSettings>) {
- const { identifier, type, sensitiveData, billing, stripe } = billingSettings;
- const settingUpdate: SettingDB = {
- identifier,
- sensitiveData,
+ public static async saveBillingSetting(tenantID: string, billingSettings: BillingSettings): Promise<string> {
+ const { id, identifier, sensitiveData, backupSensitiveData, category } = billingSettings;
+ const { createdBy, createdOn, lastChangedBy, lastChangedOn } = billingSettings;
+ const { type, billing, stripe } = billingSettings;
+ const setting: SettingDB = {
+ id, identifier, sensitiveData, backupSensitiveData, category,
+ createdBy, createdOn, lastChangedBy, lastChangedOn,
content: {
type,
billing,
- stripe
- }
+ stripe,
+ },
};
- await BillingSettingStorage._processSensitiveData(tenantID, setting, settingUpdate);
+ return SettingStorage.saveSettings(tenantID, setting);
}
- private static async _processSensitiveData(tenantID: string, setting: SettingDB, settingUpdate: SettingDB) {
- // Process the sensitive data if any
- // Preprocess the data to take care of updated values
- if (settingUpdate.sensitiveData) {
- if (!Array.isArray(settingUpdate.sensitiveData)) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.CYPHER_INVALID_SENSITIVE_DATA_ERROR,
- message: `The property 'sensitiveData' for Setting with ID '${settingUpdate.id}' is not an array`,
- module: MODULE_NAME,
- method: 'handleUpdateSetting',
- });
- }
- // Process sensitive properties
- for (const property of settingUpdate.sensitiveData) {
- // Get the sensitive property from the request
- const valueInRequest = _.get(settingUpdate, property);
- if (valueInRequest && valueInRequest.length > 0) {
- // Get the sensitive property from the DB
- const valueInDb = _.get(setting, property);
- if (valueInDb && valueInDb.length > 0) {
- const hashedValueInDB = Cypher.hash(valueInDb);
- if (valueInRequest !== hashedValueInDB) {
- // Yes: Encrypt
- _.set(settingUpdate, property, await Cypher.encrypt(tenantID, valueInRequest));
- } else {
- // No: Put back the encrypted value
- _.set(settingUpdate, property, valueInDb);
- }
- } else {
- // Value in db is empty then encrypt
- _.set(settingUpdate, property, await Cypher.encrypt(tenantID, valueInRequest));
- }
- } else {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.CYPHER_INVALID_SENSITIVE_DATA_ERROR,
- message: `The property '${property}' for Setting with ID '${settingUpdate.id}' is not set`,
- module: MODULE_NAME,
- method: 'handleUpdateSetting',
- });
- }
- }
- } else {
- settingUpdate.sensitiveData = [];
+ private static convertToBillingSettings(setting: SettingDB): BillingSettings {
+ const { id, sensitiveData, backupSensitiveData, category } = setting;
+ const { createdBy, createdOn, lastChangedBy, lastChangedOn } = setting;
+ const { content } = setting;
+
+ let billingSettings: BillingSettings = null;
+ if (content.type === BillingSettingsType.STRIPE) {
+ billingSettings = {
+ identifier: TenantComponents.BILLING,
+ type: BillingSettingsType.STRIPE,
+ id, sensitiveData, backupSensitiveData, category,
+ createdBy, createdOn, lastChangedBy, lastChangedOn,
+ billing: content.billing,
+ stripe: content.stripe
+ };
}
+ return billingSettings;
}
-
}

Billing - Settings - after sync with Serge