This commit is contained in:
Benjamin Toby 2024-12-10 15:10:32 +01:00
parent a1d2325156
commit 04a4452821
19 changed files with 161 additions and 340 deletions

4
index.d.ts vendored
View File

@ -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 };

View File

@ -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: {

View File

@ -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<import("../../../types").SendOneTimeCodeEmailResponse>;
export = _exports;
import http = require("http");

View File

@ -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<string,any>} [param.response]
*
* @returns {Promise<{success: boolean, msg?: string}>}
* @returns {Promise<import("../../../types").SendOneTimeCodeEmailResponse>}
*/
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",
};
}
};

View File

@ -4,5 +4,6 @@ declare function _exports(params?: {
}): {
keyCookieName: string;
csrfCookieName: string;
oneTimeCodeName: string;
};
export = _exports;

View File

@ -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,
};
};

View File

@ -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 {};

View File

@ -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;
};

View File

@ -1,5 +1,5 @@
declare function _exports({ request }: {
request?: http.IncomingMessage;
request: http.IncomingMessage;
}): any | null;
export = _exports;
import http = require("http");

View File

@ -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": {

View File

@ -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<string,any>} [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<boolean>}
* @returns { Promise<import("../package-shared/types").SendOneTimeCodeEmailResponse>}
*/
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<boolean>;
}): Promise<import("../package-shared/types").SendOneTimeCodeEmailResponse>;
import http = require("http");

View File

@ -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<string,any>} [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<boolean>}
* @returns { Promise<import("../package-shared/types").SendOneTimeCodeEmailResponse>}
*/
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;
}
}

19
users/validate-temp-email-code.d.ts vendored Normal file
View File

@ -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<string, any>} params.request
* @param {string} [params.email]
*
* @returns { Promise<boolean>}
*/
declare function validateTempEmailCode({ request, email }: {
request: http.IncomingMessage & {
[x: string]: any;
};
email?: string;
}): Promise<boolean>;
import http = require("http");

View File

@ -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<string, any>} params.request
* @param {string} [params.email]
*
* @returns { Promise<boolean>}
*/
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;

View File

@ -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;
};
////////////////////////////////////////
////////////////////////////////////////
////////////////////////////////////////

View File

@ -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;

View File

@ -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(/(?<!\\)\'/g, "\\'")
.replace(/(?<!\\)\`/g, "\\`")
// .replace(/(?<!\\)\"/g, '\\"')
.replace(/\/\*\*\//g, "")
.replace(escapeRegex, "\\$&");
////////////////////////////////////////
////////////////////////////////////////
////////////////////////////////////////
return finalText;
////////////////////////////////////////
////////////////////////////////////////
////////////////////////////////////////
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Sanitize Objects Function
* ==============================================================================
* @description Sanitize objects in the form { key: "value" }
*
* @param {object} object - Database Full Name
* @param {boolean?} spaces - Allow spaces
*
* @returns {object}
*/
function sanitizeObjects(object, spaces) {
let objectUpdated = { ...object };
const keys = Object.keys(objectUpdated);
keys.forEach((key) => {
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;

View File

@ -1,2 +0,0 @@
export = serializeQuery;
declare function serializeQuery(param0: import("../../package-shared/types").SerializeQueryParams): string;

View File

@ -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;