diff --git a/index.d.ts b/index.d.ts index 4a46ae3..8e319bf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -17,13 +17,13 @@ export namespace user { export { getUser }; export { getToken }; export { validateToken }; + export let validateTempEmailCode: typeof import("./users/validate-temp-email-code"); export namespace social { export { loginWithGoogle }; export { loginWithGithub }; } } import getSchema = require("./utils/get-schema"); -import sanitizeSql = require("./utils/functions/sanitizeSql"); import datasquirelClient = require("./client"); export namespace sql { export { sqlGenerator }; @@ -68,4 +68,4 @@ export declare namespace utils { }) => string; } } -export { get, post, getSchema, sanitizeSql, datasquirelClient as client }; +export { get, post, getSchema, datasquirelClient as client }; diff --git a/index.js b/index.js index 5694897..aa2408f 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,6 @@ const loginWithGithub = require("./users/social/github-auth"); const getToken = require("./users/get-token"); const validateToken = require("./users/validate-token"); -const sanitizeSql = require("./utils/functions/sanitizeSql"); const datasquirelClient = require("./client"); const sqlGenerator = require("./package-shared/functions/dsql/sql/sql-generator"); const sqlInsertGenerator = require("./package-shared/functions/dsql/sql/sql-insert-generator"); @@ -52,6 +51,7 @@ const user = { getUser: getUser, getToken: getToken, validateToken: validateToken, + validateTempEmailCode: require("./users/validate-temp-email-code"), social: { loginWithGoogle: loginWithGoogle, loginWithGithub: loginWithGithub, @@ -86,7 +86,6 @@ const datasquirel = { media, user, getSchema, - sanitizeSql, client: datasquirelClient, sql, utils: { diff --git a/package-shared/functions/api/users/api-send-email-code.d.ts b/package-shared/functions/api/users/api-send-email-code.d.ts index aeabe6b..140dc34 100644 --- a/package-shared/functions/api/users/api-send-email-code.d.ts +++ b/package-shared/functions/api/users/api-send-email-code.d.ts @@ -1,4 +1,4 @@ -declare function _exports({ email, database, email_login_field, mail_domain, mail_port, sender, mail_username, mail_password, html, useLocal, }: { +declare function _exports({ email, database, email_login_field, mail_domain, mail_port, sender, mail_username, mail_password, html, useLocal, response, }: { email: string; database: string; email_login_field?: string; @@ -9,8 +9,9 @@ declare function _exports({ email, database, email_login_field, mail_domain, mai mail_password?: string; html: string; useLocal?: boolean; -}): Promise<{ - success: boolean; - msg?: string; -}>; + response?: http.ServerResponse & { + [x: string]: any; + }; +}): Promise; export = _exports; +import http = require("http"); diff --git a/package-shared/functions/api/users/api-send-email-code.js b/package-shared/functions/api/users/api-send-email-code.js index 8f58c50..690b820 100644 --- a/package-shared/functions/api/users/api-send-email-code.js +++ b/package-shared/functions/api/users/api-send-email-code.js @@ -3,6 +3,9 @@ const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); const nodemailer = require("nodemailer"); +const http = require("http"); +const getAuthCookieNames = require("../../backend/cookies/get-auth-cookie-names"); +const encrypt = require("../../dsql/encrypt"); /** * # Send Email Login Code @@ -18,8 +21,9 @@ const nodemailer = require("nodemailer"); * @param {string} [param.mail_password] * @param {string} param.html * @param {boolean} [param.useLocal] + * @param {http.ServerResponse & Object} [param.response] * - * @returns {Promise<{success: boolean, msg?: string}>} + * @returns {Promise} */ module.exports = async function apiSendEmailCode({ email, @@ -32,6 +36,7 @@ module.exports = async function apiSendEmailCode({ mail_password, html, useLocal, + response, }) { if (email?.match(/ /)) { return { @@ -39,10 +44,7 @@ module.exports = async function apiSendEmailCode({ msg: "Invalid Email/Password format", }; } - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// + const createdAt = Date.now(); const foundUserQuery = `SELECT * FROM users WHERE email = ?`; const foundUserValues = [email]; @@ -74,7 +76,7 @@ module.exports = async function apiSendEmailCode({ return code; } - if (foundUser && foundUser[0] && email_login_field) { + if (foundUser?.[0] && email_login_field) { const tempCode = generateCode(); let transporter = nodemailer.createTransport({ @@ -102,7 +104,7 @@ module.exports = async function apiSendEmailCode({ if (!info?.accepted) throw new Error("Mail not Sent!"); const setTempCodeQuery = `UPDATE users SET ${email_login_field} = ? WHERE email = ?`; - const setTempCodeValues = [tempCode + `-${Date.now()}`, email]; + const setTempCodeValues = [tempCode + `-${createdAt}`, email]; let setTempCode = await varDatabaseDbHandler({ queryString: setTempCodeQuery, @@ -110,10 +112,34 @@ module.exports = async function apiSendEmailCode({ database: database, useLocal, }); - } - return { - success: true, - msg: "Success", - }; + /** @type {import("../../../types").SendOneTimeCodeEmailResponse} */ + const resObject = { + success: true, + code: tempCode, + email: email, + createdAt, + msg: "Success", + }; + + if (response) { + const keyNames = getAuthCookieNames(); + const oneTimeCodeCookieName = keyNames.oneTimeCodeName; + + const encryptedPayload = encrypt({ + data: JSON.stringify(resObject), + }); + + response?.setHeader("Set-Cookie", [ + `${oneTimeCodeCookieName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, + ]); + } + + return resObject; + } else { + return { + success: false, + msg: "Invalid Email/Password format", + }; + } }; diff --git a/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts b/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts index ea661a3..12aa588 100644 --- a/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts +++ b/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts @@ -4,5 +4,6 @@ declare function _exports(params?: { }): { keyCookieName: string; csrfCookieName: string; + oneTimeCodeName: string; }; export = _exports; diff --git a/package-shared/functions/backend/cookies/get-auth-cookie-names.js b/package-shared/functions/backend/cookies/get-auth-cookie-names.js index b9c498e..4603d7a 100644 --- a/package-shared/functions/backend/cookies/get-auth-cookie-names.js +++ b/package-shared/functions/backend/cookies/get-auth-cookie-names.js @@ -7,12 +7,14 @@ * @param {string} [params.database] * @param {string | number} [params.userId] * - * @returns {{ keyCookieName: string, csrfCookieName: string }} + * @returns {{ keyCookieName: string, csrfCookieName: string, oneTimeCodeName: string }} */ module.exports = function getAuthCookieNames(params) { const cookiesPrefix = process.env.DSQL_COOKIES_PREFIX || "dsql_"; const cookiesKeyName = process.env.DSQL_COOKIES_KEY_NAME || "key"; const cookiesCSRFName = process.env.DSQL_COOKIES_CSRF_NAME || "csrf"; + const cookieOneTimeCodeName = + process.env.DSQL_COOKIES_ONE_TIME_CODE_NAME || "one-time-code"; const targetDatabase = params?.database || @@ -28,8 +30,14 @@ module.exports = function getAuthCookieNames(params) { if (targetDatabase) csrfCookieName += `${targetDatabase}_`; csrfCookieName += cookiesCSRFName; + let oneTimeCodeName = cookiesPrefix; + if (params?.userId) oneTimeCodeName += `user_${params.userId}_`; + if (targetDatabase) oneTimeCodeName += `${targetDatabase}_`; + oneTimeCodeName += cookieOneTimeCodeName; + return { keyCookieName, csrfCookieName, + oneTimeCodeName, }; }; diff --git a/package-shared/types/index.d.ts b/package-shared/types/index.d.ts index b7ca79b..98da320 100644 --- a/package-shared/types/index.d.ts +++ b/package-shared/types/index.d.ts @@ -1195,4 +1195,11 @@ export interface AceEditorOptions { wrapBehavioursEnabled?: boolean; wrapMethod?: "code" | "text" | "auto"; } +export type SendOneTimeCodeEmailResponse = { + success: boolean; + code?: string; + createdAt?: number; + email?: string; + msg?: string; +}; export {}; diff --git a/package-shared/types/index.ts b/package-shared/types/index.ts index 2a26ad9..8edcdaa 100644 --- a/package-shared/types/index.ts +++ b/package-shared/types/index.ts @@ -1417,3 +1417,11 @@ export interface AceEditorOptions { wrapBehavioursEnabled?: boolean; wrapMethod?: "code" | "text" | "auto"; } + +export type SendOneTimeCodeEmailResponse = { + success: boolean; + code?: string; + createdAt?: number; + email?: string; + msg?: string; +}; diff --git a/utils/functions/parseCookies.d.ts b/package-shared/utils/backend/parseCookies.d.ts similarity index 74% rename from utils/functions/parseCookies.d.ts rename to package-shared/utils/backend/parseCookies.d.ts index f1f2d07..2b064f5 100644 --- a/utils/functions/parseCookies.d.ts +++ b/package-shared/utils/backend/parseCookies.d.ts @@ -1,5 +1,5 @@ declare function _exports({ request }: { - request?: http.IncomingMessage; + request: http.IncomingMessage; }): any | null; export = _exports; import http = require("http"); diff --git a/package.json b/package.json index e8f858c..5787c3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/datasquirel", - "version": "2.8.2", + "version": "2.8.3", "description": "Cloud-based SQL data management tool", "main": "index.js", "bin": { diff --git a/users/send-email-code.d.ts b/users/send-email-code.d.ts index cbbf816..72ddc5c 100644 --- a/users/send-email-code.d.ts +++ b/users/send-email-code.d.ts @@ -14,8 +14,8 @@ export = sendEmailCode; * @param {String} [params.key] - FULL ACCESS API Key * @param {String} [params.database] - Target Database * @param {string} params.email Login Email/Username and Password - * @param {http.ServerResponse} [params.response] - Http response object * @param {string} [params.temp_code_field_name] - Database table field name for temporary code + * @param {http.ServerResponse & Object} [params.response] * @param {string} [params.mail_domain] * @param {string} [params.mail_username] * @param {string} [params.mail_password] @@ -24,14 +24,16 @@ export = sendEmailCode; * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * - * @returns { Promise} + * @returns { Promise} */ -declare function sendEmailCode({ key, email, database, temp_code_field_name, mail_domain, mail_password, mail_username, mail_port, sender, user_id, useLocal, }: { +declare function sendEmailCode({ key, email, database, temp_code_field_name, mail_domain, mail_password, mail_username, mail_port, sender, user_id, useLocal, response, }: { key?: string; database?: string; email: string; - response?: http.ServerResponse; temp_code_field_name?: string; + response?: http.ServerResponse & { + [x: string]: any; + }; mail_domain?: string; mail_username?: string; mail_password?: string; @@ -39,5 +41,5 @@ declare function sendEmailCode({ key, email, database, temp_code_field_name, mai sender?: string; user_id?: boolean; useLocal?: boolean; -}): Promise; +}): Promise; import http = require("http"); diff --git a/users/send-email-code.js b/users/send-email-code.js index 336623f..556413c 100644 --- a/users/send-email-code.js +++ b/users/send-email-code.js @@ -6,7 +6,6 @@ * ============================================================================== */ const http = require("http"); -const https = require("https"); const fs = require("fs"); const path = require("path"); const grabHostNames = require("../package-shared/utils/grab-host-names"); @@ -28,8 +27,8 @@ const apiSendEmailCode = require("../package-shared/functions/api/users/api-send * @param {String} [params.key] - FULL ACCESS API Key * @param {String} [params.database] - Target Database * @param {string} params.email Login Email/Username and Password - * @param {http.ServerResponse} [params.response] - Http response object * @param {string} [params.temp_code_field_name] - Database table field name for temporary code + * @param {http.ServerResponse & Object} [params.response] * @param {string} [params.mail_domain] * @param {string} [params.mail_username] * @param {string} [params.mail_password] @@ -38,7 +37,7 @@ const apiSendEmailCode = require("../package-shared/functions/api/users/api-send * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * - * @returns { Promise} + * @returns { Promise} */ async function sendEmailCode({ key, @@ -52,6 +51,7 @@ async function sendEmailCode({ sender, user_id, useLocal, + response, }) { const grabedHostNames = grabHostNames(); const { host, port, scheme } = grabedHostNames; @@ -66,11 +66,6 @@ async function sendEmailCode({ "utf-8" ); - /** - * Initialize HTTP response variable - */ - let httpResponse; - /** * Check for local DB settings * @@ -97,7 +92,7 @@ async function sendEmailCode({ dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); } catch (error) {} - httpResponse = await apiSendEmailCode({ + return await apiSendEmailCode({ database: DSQL_DB_NAME, email, email_login_field: emailLoginTempCodeFieldName, @@ -108,6 +103,7 @@ async function sendEmailCode({ mail_username, sender, useLocal, + response, }); } else { /** @@ -115,9 +111,9 @@ async function sendEmailCode({ * * @description make a request to datasquirel.com * - * @type {{ success: boolean, payload: import("../package-shared/types").DATASQUIREL_LoggedInUser | null, userId?: number, msg?: string }} + * @type {import("../package-shared/types").SendOneTimeCodeEmailResponse} */ - httpResponse = await new Promise((resolve, reject) => { + const httpResponse = await new Promise((resolve, reject) => { const reqPayload = JSON.stringify({ email, database, @@ -173,22 +169,8 @@ async function sendEmailCode({ httpsRequest.write(reqPayload); httpsRequest.end(); }); - } - /** ********************************************** */ - /** ********************************************** */ - /** ********************************************** */ - - /** - * Make https request - * - * @description make a request to datasquirel.com - */ - if (httpResponse?.success) { - return true; - } else { - console.log(httpResponse); - return false; + return httpResponse; } } diff --git a/users/validate-temp-email-code.d.ts b/users/validate-temp-email-code.d.ts new file mode 100644 index 0000000..ddb930b --- /dev/null +++ b/users/validate-temp-email-code.d.ts @@ -0,0 +1,19 @@ +export = validateTempEmailCode; +/** + * Verify the temp email code sent to the user's email address + * ============================================================================== + * @async + * + * @param {object} params - Single Param object containing params + * @param {http.IncomingMessage & Object} params.request + * @param {string} [params.email] + * + * @returns { Promise} + */ +declare function validateTempEmailCode({ request, email }: { + request: http.IncomingMessage & { + [x: string]: any; + }; + email?: string; +}): Promise; +import http = require("http"); diff --git a/users/validate-temp-email-code.js b/users/validate-temp-email-code.js new file mode 100644 index 0000000..c4f4782 --- /dev/null +++ b/users/validate-temp-email-code.js @@ -0,0 +1,53 @@ +// @ts-check + +const http = require("http"); +const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); +const parseCookies = require("../package-shared/utils/backend/parseCookies"); +const decrypt = require("../package-shared/functions/dsql/decrypt"); +const EJSON = require("../package-shared/utils/ejson"); + +/** + * Verify the temp email code sent to the user's email address + * ============================================================================== + * @async + * + * @param {object} params - Single Param object containing params + * @param {http.IncomingMessage & Object} params.request + * @param {string} [params.email] + * + * @returns { Promise} + */ +async function validateTempEmailCode({ request, email }) { + try { + const keyNames = getAuthCookieNames(); + const oneTimeCodeCookieName = keyNames.oneTimeCodeName; + + const cookies = parseCookies({ request }); + const encryptedOneTimeCode = cookies[oneTimeCodeCookieName]; + + const encryptedPayload = decrypt({ + encryptedString: encryptedOneTimeCode, + }); + + const payload = + /** @type {import("../package-shared/types").SendOneTimeCodeEmailResponse | undefined} */ ( + EJSON.parse(encryptedPayload) + ); + + if (payload?.email && !email) { + return true; + } + + if (payload?.email && payload.email === email) { + return true; + } + + return false; + } catch (/** @type {any} */ error) { + console.log("validateTempEmailCode error:", error.message); + + return false; + } +} + +module.exports = validateTempEmailCode; diff --git a/utils/functions/parseCookies.js b/utils/functions/parseCookies.js deleted file mode 100644 index 0c1210f..0000000 --- a/utils/functions/parseCookies.js +++ /dev/null @@ -1,68 +0,0 @@ -// @ts-check - -/** - * ============================================================================== - * Imports - * ============================================================================== - */ -const http = require("http"); - -/** - * Parse request cookies - * ============================================================================== - * - * @description This function takes in a request object and returns the cookies as a JS object - * - * @async - * - * @param {object} params - main params object - * @param {http.IncomingMessage} [params.request] - HTTPS request object - * - * @returns {* | null} - */ -module.exports = function ({ request }) { - if (!request) return {}; - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - /** @type {string | undefined} */ - const cookieString = request.headers.cookie; - - if (!cookieString || typeof cookieString !== "string") { - return null; - } - - /** @type {string[]} */ - const cookieSplitArray = cookieString.split(";"); - - /** @type {*} */ - let cookieObject = {}; - - cookieSplitArray.forEach((keyValueString) => { - const [key, value] = keyValueString.split("="); - if (key && typeof key == "string") { - cookieObject[key.replace(/^ +| +$/, "")] = - value && typeof value == "string" - ? value.replace(/^ +| +$/, "") - : null; - } - }); - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - /** - * Make https request - * - * @description make a request to datasquirel.com - */ - - return cookieObject; -}; - -//////////////////////////////////////// -//////////////////////////////////////// -//////////////////////////////////////// diff --git a/utils/functions/sanitizeSql.d.ts b/utils/functions/sanitizeSql.d.ts deleted file mode 100644 index 2621202..0000000 --- a/utils/functions/sanitizeSql.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export = sanitizeSql; -/** - * Sanitize SQL function - * ============================================================================== - * @description this function takes in a text(or number) or object or array or - * boolean and returns a sanitized version of the same input. - * - * @param {string|number|object|boolean} input - Text or number or object or boolean - * @param {boolean?} spaces - Allow spaces? - * - * @returns {string|number|object|boolean} - */ -declare function sanitizeSql(input: string | number | object | boolean, spaces: boolean | null): string | number | object | boolean; diff --git a/utils/functions/sanitizeSql.js b/utils/functions/sanitizeSql.js deleted file mode 100644 index 4a3583a..0000000 --- a/utils/functions/sanitizeSql.js +++ /dev/null @@ -1,184 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -/** - * Sanitize SQL function - * ============================================================================== - * @description this function takes in a text(or number) or object or array or - * boolean and returns a sanitized version of the same input. - * - * @param {string|number|object|boolean} input - Text or number or object or boolean - * @param {boolean?} spaces - Allow spaces? - * - * @returns {string|number|object|boolean} - */ -function sanitizeSql(input, spaces) { - /** - * Initial Checks - * - * @description Initial Checks - */ - if (!input) return ""; - if (typeof input == "number" || typeof input == "boolean") return input; - if (typeof input == "string" && !input?.toString()?.match(/./)) return ""; - - if (typeof input == "object" && !Array.isArray(input)) { - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - const newObject = sanitizeObjects(input, spaces); - return newObject; - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - } else if (typeof input == "object" && Array.isArray(input)) { - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - const newArray = sanitizeArrays(input, spaces); - return newArray; - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - } - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - /** - * Declare variables - * - * @description Declare "results" variable - */ - let finalText = input; - - if (spaces) { - } else { - finalText = input - .toString() - .replace(/\n|\r|\n\r|\r\n/g, "") - .replace(/ /g, ""); - } - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - const escapeRegex = /select |insert |drop |delete |alter |create |exec | union | or | like | concat|LOAD_FILE|ASCII| COLLATE | HAVING | information_schema|DECLARE |\#|WAITFOR |delay |BENCHMARK |\/\*.*\*\//gi; - - finalText = finalText - .replace(/(? { - const value = objectUpdated[key]; - - if (!value) { - delete objectUpdated[key]; - return; - } - - if (typeof value == "string" || typeof value == "number") { - objectUpdated[key] = sanitizeSql(value, spaces); - } else if (typeof value == "object" && !Array.isArray(value)) { - objectUpdated[key] = sanitizeObjects(value, spaces); - } else if (typeof value == "object" && Array.isArray(value)) { - objectUpdated[key] = sanitizeArrays(value, spaces); - } - }); - - return objectUpdated; -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -/** - * Sanitize Objects Function - * ============================================================================== - * @description Sanitize objects in the form { key: "value" } - * - * @param {string[]|number[]|object[]} array - Database Full Name - * @param {boolean?} spaces - Allow spaces - * - * @returns {string[]|number[]|object[]} - */ -function sanitizeArrays(array, spaces) { - let arrayUpdated = [...array]; - - arrayUpdated.forEach((item, index) => { - const value = item; - - if (!value) { - arrayUpdated.splice(index, 1); - return; - } - - if (typeof item == "string" || typeof item == "number") { - arrayUpdated[index] = sanitizeSql(value, spaces); - } else if (typeof item == "object" && !Array.isArray(value)) { - arrayUpdated[index] = sanitizeObjects(value, spaces); - } else if (typeof item == "object" && Array.isArray(value)) { - arrayUpdated[index] = sanitizeArrays(item, spaces); - } - }); - - return arrayUpdated; -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -module.exports = sanitizeSql; diff --git a/utils/functions/serialize-query.d.ts b/utils/functions/serialize-query.d.ts deleted file mode 100644 index 3a27633..0000000 --- a/utils/functions/serialize-query.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export = serializeQuery; -declare function serializeQuery(param0: import("../../package-shared/types").SerializeQueryParams): string; diff --git a/utils/functions/serialize-query.js b/utils/functions/serialize-query.js deleted file mode 100644 index 4ebff2a..0000000 --- a/utils/functions/serialize-query.js +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-check - -/** @type {import("../../package-shared/types").SerializeQueryFnType} */ -function serializeQuery({ query }) { - let str = "?"; - const keys = Object.keys(query); - - /** @type {string[]} */ - const queryArr = []; - keys.forEach((key) => { - if (!key || !query[key]) return; - queryArr.push(`${key}=${query[key]}`); - }); - str += queryArr.join("&"); - return str; -} - -module.exports = serializeQuery;