From 705f2a34e41905c639de0cc70320e322dbf09604 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 19 Apr 2022 19:00:53 +0100 Subject: [PATCH] fix mailservice --- Common/Types/Char.ts | 65 ++++++++ Common/Types/JSON.ts | 1 + CommonServer/Infrastructure/ORM.ts | 4 + CommonServer/Models/SsoDefaultRole.ts | 2 +- CommonServer/Services/DatabaseService.ts | 198 ++++++++++++++--------- CommonServer/Types/DB/FindOneBy.ts | 4 +- CommonServer/Types/DB/Query.ts | 46 +++++- CommonServer/Utils/DBFunctions.ts | 20 +++ CommonServer/Utils/Encryption.ts | 6 +- Mail/API/Mail.ts | 16 +- Mail/Services/MailService.ts | 23 +-- 11 files changed, 284 insertions(+), 101 deletions(-) create mode 100644 Common/Types/Char.ts create mode 100644 CommonServer/Utils/DBFunctions.ts diff --git a/Common/Types/Char.ts b/Common/Types/Char.ts new file mode 100644 index 0000000000..3d695c9331 --- /dev/null +++ b/Common/Types/Char.ts @@ -0,0 +1,65 @@ +type Char = + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' + | 'A' + | 'B' + | 'C' + | 'D' + | 'E' + | 'F' + | 'G' + | 'H' + | 'I' + | 'J' + | 'K' + | 'L' + | 'M' + | 'N' + | 'O' + | 'P' + | 'Q' + | 'R' + | 'S' + | 'T' + | 'U' + | 'V' + | 'W' + | 'X' + | 'Y' + | 'Z' + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9'; + +export default Char; diff --git a/Common/Types/JSON.ts b/Common/Types/JSON.ts index 3db5f3d721..9995c0cf77 100644 --- a/Common/Types/JSON.ts +++ b/Common/Types/JSON.ts @@ -13,6 +13,7 @@ export type JSONValue = | Array | ObjectID | Array + | Buffer | null; export interface JSONObject { diff --git a/CommonServer/Infrastructure/ORM.ts b/CommonServer/Infrastructure/ORM.ts index 27b50a2c61..854929d99a 100644 --- a/CommonServer/Infrastructure/ORM.ts +++ b/CommonServer/Infrastructure/ORM.ts @@ -39,3 +39,7 @@ export interface UniqueFields extends Array {} export interface EncryptedFields extends Array {} export class Schema extends mongoose.Schema {} + +export type Query = mongoose.FilterQuery; + +export type Populate = mongoose.PopulateOptions; diff --git a/CommonServer/Models/SsoDefaultRole.ts b/CommonServer/Models/SsoDefaultRole.ts index b39a7548a9..6ba10d4e51 100644 --- a/CommonServer/Models/SsoDefaultRole.ts +++ b/CommonServer/Models/SsoDefaultRole.ts @@ -21,7 +21,7 @@ const schema: Schema = new Schema({ role: { type: String, required: true, - enum: RoleArray.filter((item: $TSFixMe) => { + enum: RoleArray.filter((item: string) => { return item !== 'Owner'; }), // All roles except Owner }, diff --git a/CommonServer/Services/DatabaseService.ts b/CommonServer/Services/DatabaseService.ts index c399aa488e..7c698d8339 100644 --- a/CommonServer/Services/DatabaseService.ts +++ b/CommonServer/Services/DatabaseService.ts @@ -19,6 +19,10 @@ import OneUptimeDate from 'Common/Types/Date'; import Exception from 'Common/Types/Exception/Exception'; import SearchResult from '../Types/DB/SearchResult'; import Encryption from '../Utils/Encryption'; +import { Query as DbQuery } from '../Infrastructure/ORM'; +import { JSONObject } from 'Common/Types/JSON'; +import SortOrder from '../Types/DB/SortOrder'; +import DbFunctions from '../Utils/DBFunctions'; export interface ListProps { populate: Populate; @@ -51,8 +55,8 @@ class DatabaseService { public memberItemProps: ItemProps; public memberListProps: ListProps; public model: Model; - public ItemProps: ItemProps; - public ListProps: ListProps; + public publicItemProps: ItemProps; + public publicListProps: ListProps; public requiredFields: RequiredFields; public uniqueFields: UniqueFields; public encryptedFields: EncryptedFields; @@ -117,7 +121,7 @@ class DatabaseService { this.encryptedFields = encryptedFields; } - protected isValid(data: Document): boolean { + protected isValid(data: JSONObject): boolean { if (!data) { throw new BadDataException('Data cannot be null'); } @@ -125,10 +129,10 @@ class DatabaseService { return true; } - protected checkRequiredFields(data: Document): Promise { + protected checkRequiredFields(data: JSONObject): void { // Check required fields. for (const requiredField of this.requiredFields) { - if (!data.get(requiredField)) { + if (!data[requiredField]) { throw new BadDataException(`${requiredField} is required`); } } @@ -139,23 +143,26 @@ class DatabaseService { return Promise.resolve({ data } as CreateBy); } - protected encrypt(data: Document): Document { + protected encrypt(data: JSONObject): JSONObject { const iv: Buffer = Encryption.getIV(); - data.set('iv', iv); + data['iv'] = iv; for (const key of this.encryptedFields) { // If data is an object. - if (typeof data.get(key) === 'object') { - const dataObj: $TSFixMe = data.get(key); + if (typeof data[key] === 'object') { + const dataObj: JSONObject = data[key] as JSONObject; for (const key in dataObj) { - dataObj[key] = Encryption.encrypt(dataObj[key], iv); + dataObj[key] = Encryption.encrypt( + dataObj[key] as string, + iv + ); } - data.set(key, dataObj); + data[key] = dataObj; } else { //If its string or other type. - data.set(key, Encryption.encrypt(data.get(key), iv)); + data[key] = Encryption.encrypt(data[key] as string, iv); } } @@ -168,10 +175,13 @@ class DatabaseService { for (const key of this.encryptedFields) { // If data is an object. if (typeof data.get(key) === 'object') { - const dataObj: $TSFixMe = data.get(key); + const dataObj: JSONObject = data.get(key); for (const key in dataObj) { - dataObj[key] = Encryption.decrypt(dataObj[key], iv); + dataObj[key] = Encryption.decrypt( + dataObj[key] as string, + iv + ); } data.set(key, dataObj); @@ -258,11 +268,11 @@ class DatabaseService { } public async create(createBy: CreateBy): Promise { - const _createdBy: $TSFixMe = await this.onBeforeCreate({ + const _createdBy: CreateBy = await this.onBeforeCreate({ data: createBy.data, }); - let data: $TSFixMe = _createdBy.data; + let data: JSONObject = _createdBy.data; this.checkRequiredFields(data); @@ -274,20 +284,35 @@ class DatabaseService { data = this.encrypt(data); try { - const item: $TSFixMe = new this.model(); + const item: Document = new this.model(); if (this.uniqueFields && this.uniqueFields.length > 0) { - const countQuery: Query = {}; + const countQuery: Query = new Query(); if (this.isResourceByProject) { - countQuery.projectId = data.get('projectId'); + countQuery.equalTo( + 'projectId', + data['projectId'] as string + ); } for (const duplicateValueIn of this.uniqueFields) { - countQuery[duplicateValueIn] = data.get(duplicateValueIn); + if (typeof data[duplicateValueIn] === 'number') { + countQuery.equalTo( + duplicateValueIn, + data[duplicateValueIn] as number + ); + } + + if (typeof data[duplicateValueIn] === 'string') { + countQuery.equalTo( + duplicateValueIn, + data[duplicateValueIn] as string + ); + } } - const existingItemCount: $TSFixMe = await this.countBy({ + const existingItemCount: PositiveNumber = await this.countBy({ query: countQuery, }); @@ -303,11 +328,14 @@ class DatabaseService { } for (const key in data) { - item.set(key, data.get(key)); + item.set(key, data[key]); } if (this.slugifyField) { - item.set('slug', Slug.getSlug(data.get(this.slugifyField))); + item.set( + 'slug', + Slug.getSlug(data[this.slugifyField] as string) + ); } await this.onCreateSuccess(item); @@ -319,9 +347,9 @@ class DatabaseService { } } - public async countBy({ query = {} }: CountBy): Promise { + public async countBy({ query }: CountBy): Promise { try { - query.deleted = false; + query.equalTo('deleted', false); const count: number = await this.model.countDocuments(query); let countPositive: PositiveNumber = new PositiveNumber(count); countPositive = await this.onCountSuccess(countPositive); @@ -333,7 +361,7 @@ class DatabaseService { } public async deleteOneBy({ - query = {}, + query, deletedByUserId, }: DeleteOneBy): Promise { return await this._deleteBy({ @@ -343,10 +371,7 @@ class DatabaseService { }); } - public async deleteBy({ - query = {}, - deletedByUserId, - }: DeleteBy): Promise { + public async deleteBy({ query, deletedByUserId }: DeleteBy): Promise { return await this._deleteBy({ query, multiple: false, @@ -354,24 +379,28 @@ class DatabaseService { }); } + public async hardDeleteBy(query: Query): Promise { + return await this._hardDeleteBy(query); + } + private async _hardDeleteBy(query: Query): Promise { - await this.model.remove(query); + return await this.model.remove(query); } private async _deleteBy({ - query = {}, + query, multiple = false, deletedByUserId, }: InternalDeleteBy): Promise { try { - const beforeDeleteBy: $TSFixMe = await this.onBeforeDelete({ + const beforeDeleteBy: DeleteBy = await this.onBeforeDelete({ query, deletedByUserId, }); - query.deleted = false; + query.equalTo('deleted', false); - const item: $TSFixMe = new this.model(); + const item: Document = new this.model(); item.set('deleted', true); item.set('deletedById', beforeDeleteBy.deletedByUserId); item.set('deletedAt', OneUptimeDate.getCurrentDate()); @@ -396,11 +425,11 @@ class DatabaseService { } public async getListForViewer({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), sort, - }: FindBy): Array { + }: FindBy): Promise> { return await this.findBy({ query, skip, @@ -412,11 +441,11 @@ class DatabaseService { } public async getListForAdmin({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), sort, - }: FindBy): Array { + }: FindBy): Promise> { return await this.findBy({ query, skip, @@ -428,11 +457,11 @@ class DatabaseService { } public async getListForOwner({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), sort, - }: FindBy): Array { + }: FindBy): Promise> { return await this.findBy({ query, skip, @@ -444,11 +473,11 @@ class DatabaseService { } public async getListForMember({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), sort, - }: FindBy): Array { + }: FindBy): Promise> { return await this.findBy({ query, skip, @@ -460,11 +489,11 @@ class DatabaseService { } public async getListForPublic({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), sort, - }: FindBy): Array { + }: FindBy): Promise> { return await this.findBy({ query, skip, @@ -476,7 +505,7 @@ class DatabaseService { } public async getItemForViewer({ - query = {}, + query, sort, }: FindOneBy): Promise { return await this.findOneBy({ @@ -488,7 +517,7 @@ class DatabaseService { } public async getItemForAdmin({ - query = {}, + query, sort, }: FindOneBy): Promise { return await this.findOneBy({ @@ -500,7 +529,7 @@ class DatabaseService { } public async getItemForMember({ - query = {}, + query, sort, }: FindOneBy): Promise { return await this.findOneBy({ @@ -512,7 +541,7 @@ class DatabaseService { } public async getItemForOwner({ - query = {}, + query, sort, }: FindOneBy): Promise { return await this.findOneBy({ @@ -524,7 +553,7 @@ class DatabaseService { } public async getItemForPublic({ - query = {}, + query, sort, }: FindOneBy): Promise { return await this.findOneBy({ @@ -536,7 +565,7 @@ class DatabaseService { } public async findBy({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), populate, @@ -555,7 +584,7 @@ class DatabaseService { } private async _findBy({ - query = {}, + query, skip = new PositiveNumber(0), limit = new PositiveNumber(10), populate, @@ -564,7 +593,7 @@ class DatabaseService { multiple = false, }: InternalFindBy): Promise> { try { - const onBeforeFind: $TSFixMe = await this.onBeforeFind({ + const onBeforeFind: FindBy = await this.onBeforeFind({ query, skip, limit, @@ -573,28 +602,35 @@ class DatabaseService { sort, }); - query.deleted = false; + onBeforeFind.query.equalTo('deleted', false); - let dbQuery: $TSFixMe = null; + const dbQuery: DbQuery = + onBeforeFind.query.asOrmQuery(); if (!multiple) { - dbQuery = this.model.findOne(onBeforeFind.query); - } else { - dbQuery = this.model.find(onBeforeFind.query); + limit = new PositiveNumber(1); } - dbQuery + if (!onBeforeFind.sort) { + onBeforeFind.sort = [ + { + createdAt: SortOrder.Descending, + }, + ]; + } + + //convert populate to dbpopulate + + const items: Array = (await this.model + .find(dbQuery) .sort(onBeforeFind.sort) .limit(onBeforeFind.limit.toNumber()) .skip(onBeforeFind.skip.toNumber()) - .lean(); + .select(onBeforeFind.select) + .populate(DbFunctions.toDbPopulate(onBeforeFind.populate)) + .lean()) as Array; - dbQuery.select(onBeforeFind.select); - dbQuery.populate(onBeforeFind.populate); - - const items: Function = (await dbQuery) as Array; - - const decryptedItems: $TSFixMe = []; + const decryptedItems: Array = []; for (const item of items) { decryptedItems.push(this.decrypt(item)); @@ -610,12 +646,12 @@ class DatabaseService { } public async findOneBy({ - query = {}, + query, populate = [], select = ['_id'], sort = [], }: FindOneBy): Promise { - const documents: $TSFixMe = await this._findBy({ + const documents: Array = await this._findBy({ query, skip: new PositiveNumber(0), limit: new PositiveNumber(1), @@ -637,11 +673,11 @@ class DatabaseService { multiple = true, }: InternalUpdateBy): Promise { try { - if (!query.deleted) { - query.deleted = false; + if (!query.hasQueryFor('deleted')) { + query.equalTo('deleted', false); } - const beforeUpdateBy: $TSFixMe = await this.onBeforeUpdate({ + const beforeUpdateBy: UpdateBy = await this.onBeforeUpdate({ query, data, }); @@ -686,14 +722,20 @@ class DatabaseService { select, populate, }: SearchBy): Promise { - const query: Query = { - [column]: { $regex: new RegExp(text), $options: 'i' }, - }; + const query: Query = new Query().regexp(column, text, 'i'); - const [items, count]: $TSFixMe = await Promise.all([ - this.findBy({ query, skip, limit, select, populate }), - this.countBy({ query }), - ]); + const [items, count]: [Array, PositiveNumber] = + await Promise.all([ + this.findBy({ + query, + skip, + limit, + select, + populate, + sort: undefined, + }), + this.countBy({ query }), + ]); return { items, count }; } diff --git a/CommonServer/Types/DB/FindOneBy.ts b/CommonServer/Types/DB/FindOneBy.ts index f575ef03c3..5125c6c04d 100644 --- a/CommonServer/Types/DB/FindOneBy.ts +++ b/CommonServer/Types/DB/FindOneBy.ts @@ -6,6 +6,6 @@ import Sort from './Sort'; export default interface FindOneBy { query: Query; select: Select; - populate?: Populate; - sort?: Sort; + populate: Populate | undefined; + sort: Sort | undefined; } diff --git a/CommonServer/Types/DB/Query.ts b/CommonServer/Types/DB/Query.ts index 3721bd3834..0c7f28333f 100644 --- a/CommonServer/Types/DB/Query.ts +++ b/CommonServer/Types/DB/Query.ts @@ -1,5 +1,47 @@ import { JSONValue } from 'Common/Types/JSON'; +import { Query as DbQuery } from '../../Infrastructure/ORM'; -export default interface Query { - [x: string]: JSONValue | RegExp | Query; +export interface QueryType { + [x: string]: JSONValue | RegExp | QueryType; +} + +export default class Query { + private query: QueryType = {}; + + public constructor() { + this.query = {}; + } + + public equalTo(column: string, value: JSONValue): Query { + this.query[column] = value; + return this; + } + + public notEqualTo(column: string, value: JSONValue): Query { + this.query[column] = { $not: value }; + return this; + } + + public regexp(column: string, text: string, options: string): Query { + this.query[column] = { $regex: new RegExp(text), $options: options }; + return this; + } + + public hasQueryFor(column: string): boolean { + if (this.query[column]) { + return true; + } + + return false; + } + + public asOrmQuery(): DbQuery { + const dbQuery: DbQuery = {}; + + for (const key in this.query) { + dbQuery[key] = this.query[key]; + } + + return dbQuery; + } } diff --git a/CommonServer/Utils/DBFunctions.ts b/CommonServer/Utils/DBFunctions.ts new file mode 100644 index 0000000000..854e77650f --- /dev/null +++ b/CommonServer/Utils/DBFunctions.ts @@ -0,0 +1,20 @@ +import Populate from '../Types/DB/Populate'; +import { Populate as DbPopulate } from '../Infrastructure/ORM'; + +export default class DbFunctions { + public static toDbPopulate(populate?: Populate): Array { + const dbPopulate: Array = []; + + if (populate && populate.length > 1) { + for (const item of populate) { + dbPopulate.push({ + select: item.select, + populate: this.toDbPopulate(item.populate), + path: item.path, + }); + } + } + + return dbPopulate; + } +} diff --git a/CommonServer/Utils/Encryption.ts b/CommonServer/Utils/Encryption.ts index 49e7040c4f..fa31bf8cf7 100644 --- a/CommonServer/Utils/Encryption.ts +++ b/CommonServer/Utils/Encryption.ts @@ -3,7 +3,7 @@ import { EncryptionSecret } from '../Config'; export default class Encryption { public static encrypt(text: string, iv: Buffer): string { - const cipher: $TSFixMe = Crypto.createCipheriv( + const cipher: Crypto.Cipher = Crypto.createCipheriv( 'aes-256-cbc', EncryptionSecret, iv @@ -12,7 +12,7 @@ export default class Encryption { } public static decrypt(encrypted: string, iv: Buffer): string { - const decipher: $TSFixMe = Crypto.createDecipheriv( + const decipher: Crypto.Cipher = Crypto.createDecipheriv( 'aes-256-cbc', EncryptionSecret, iv @@ -23,7 +23,7 @@ export default class Encryption { } public static getIV(): Buffer { - const iv: $TSFixMe = Crypto.randomBytes(16); + const iv: Buffer = Crypto.randomBytes(16); return iv; } } diff --git a/Mail/API/Mail.ts b/Mail/API/Mail.ts index 6e9935ab8c..c75f697e29 100644 --- a/Mail/API/Mail.ts +++ b/Mail/API/Mail.ts @@ -13,26 +13,30 @@ import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthoriza import MailService from '../Services/MailService'; import Mail from '../Types/Mail'; import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; +import { JSONObject } from 'Common/Types/JSON'; +import Email from 'Common/Types/Email'; +import Dictionary from 'Common/Types/Dictionary'; +import ObjectID from 'Common/Types/ObjectID'; router.post( '/:template-name', ClusterKeyAuthorization.isAuthorizedService, async (req: ExpressRequest, res: ExpressResponse) => { try { - const body: $TSFixMe = req.body; + const body: JSONObject = req.body; const mail: Mail = { templateType: req.params['template-name'] as EmailTemplateType, - toEmail: body.toEmail, - subject: body.subject, - vars: body.vars, + toEmail: new Email(body['toEmail'] as string), + subject: body['subject'] as string, + vars: body['vars'] as Dictionary, body: '', }; await MailService.send( mail, - body.projectId, - body.forceSendFromGlobalMailServer + new ObjectID(body['projectId'] as string), + body['forceSendFromGlobalMailServer'] as boolean ); return sendEmptyResponse(req, res); diff --git a/Mail/Services/MailService.ts b/Mail/Services/MailService.ts index 03f83897a4..09110a8688 100755 --- a/Mail/Services/MailService.ts +++ b/Mail/Services/MailService.ts @@ -19,11 +19,14 @@ import Dictionary from 'Common/Types/Dictionary'; import TaskStatus from 'Common/Types/TaskStatus'; import Hostname from 'Common/Types/API/Hostname'; import Exception from 'Common/Types/Exception/Exception'; +import { Document } from 'CommonServer/Infrastructure/ORM'; +import Select from 'CommonServer/Types/DB/Select'; +import Query from 'CommonServer/Types/DB/Query'; export default class MailService { private static async getGlobalSmtpSettings(): Promise { - const document: $TSFixMe = await GlobalConfigService.findOneBy({ - query: { name: 'smtp' }, + const document: Document | null = await GlobalConfigService.findOneBy({ + query: new Query().equalTo('name', 'smtp'), select: ['value'], populate: [], sort: [], @@ -93,7 +96,7 @@ export default class MailService { private static async getProjectSmtpSettings( projectId: ObjectID ): Promise { - const select: $TSFixMe = [ + const select: Select = [ 'user', 'pass', 'host', @@ -103,8 +106,10 @@ export default class MailService { 'secure', ]; - const projectSmtp: $TSFixMe = await EmailSmtpService.findOneBy({ - query: { projectId, enabled: true }, + const projectSmtp: Document | null = await EmailSmtpService.findOneBy({ + query: new Query() + .equalTo('projectId', projectId) + .equalTo('enabled', true), select, populate: [], sort: [], @@ -131,7 +136,7 @@ export default class MailService { ): Promise { // Localcache templates, so we dont read from disk all the time. - let templateData: $TSFixMe; + let templateData: string; if (LocalCache.hasValue(emailTemplateType)) { templateData = LocalCache.get(emailTemplateType); } else { @@ -161,11 +166,11 @@ export default class MailService { } private static createMailer(mailServer: MailServer): Transporter { - const helpers: $TSFixMe = { - year: OneUptimeDate.getCurrentYear(), + const helpers: Dictionary = { + year: OneUptimeDate.getCurrentYear().toString(), }; - const options: $TSFixMe = { + const options: hbs.NodemailerExpressHandlebarsOptions = { viewEngine: { extname: '.hbs', layoutsDir: 'Templates',