From 43287b032979abaa4742979e1e32092a1ab50e8b Mon Sep 17 00:00:00 2001 From: Tben Date: Sun, 13 Aug 2023 14:00:04 +0100 Subject: [PATCH] Upgrades --- .../engine/utils/camelJoinedtoCamelSpace.js | 6 +- engine/engine/utils/varDatabaseDbHandler.js | 12 +- engine/query/utils/runQuery.js | 4 +- engine/user/add-user.js | 4 + engine/user/get-user.js | 53 +++ engine/user/login-user.js | 146 +++++++ engine/user/reauth-user.js | 105 +++++ engine/user/social/github-auth.js | 133 ++++++ engine/user/social/google-auth.js | 161 ++++++++ engine/user/social/utils/githubLogin.js | 160 ++++++++ engine/user/social/utils/googleLogin.js | 142 +++++++ engine/user/social/utils/handleSocialDb.js | 377 ++++++++++++++++++ engine/user/social/utils/httpsRequest.js | 107 +++++ engine/user/update-user.js | 89 +++++ functions/decrypt.js | 4 - functions/encrypt.js | 15 +- functions/hashPassword.js | 5 +- package-lock.json | 236 ++++++++++- package.json | 3 +- users/get-user.js | 44 +- users/login-user.js | 118 +++--- users/reauth-user.js | 114 ++++-- users/social/github-auth.js | 148 ++++--- users/social/google-auth.js | 126 +++--- users/update-user.js | 24 ++ 25 files changed, 2106 insertions(+), 230 deletions(-) create mode 100644 engine/user/get-user.js create mode 100644 engine/user/login-user.js create mode 100644 engine/user/reauth-user.js create mode 100644 engine/user/social/github-auth.js create mode 100644 engine/user/social/google-auth.js create mode 100644 engine/user/social/utils/githubLogin.js create mode 100644 engine/user/social/utils/googleLogin.js create mode 100644 engine/user/social/utils/handleSocialDb.js create mode 100644 engine/user/social/utils/httpsRequest.js create mode 100644 engine/user/update-user.js diff --git a/engine/engine/utils/camelJoinedtoCamelSpace.js b/engine/engine/utils/camelJoinedtoCamelSpace.js index 215b1cd..7435818 100644 --- a/engine/engine/utils/camelJoinedtoCamelSpace.js +++ b/engine/engine/utils/camelJoinedtoCamelSpace.js @@ -10,7 +10,7 @@ * * @returns {string | null} */ -module.exports = function camelJoinedtoCamelSpace(text) { +function camelJoinedtoCamelSpace(text) { if (!text?.match(/./)) { return ""; } @@ -49,4 +49,6 @@ module.exports = function camelJoinedtoCamelSpace(text) { } else { return null; } -}; +} + +module.exports = camelJoinedtoCamelSpace; diff --git a/engine/engine/utils/varDatabaseDbHandler.js b/engine/engine/utils/varDatabaseDbHandler.js index 8dfdfd5..4762c7a 100644 --- a/engine/engine/utils/varDatabaseDbHandler.js +++ b/engine/engine/utils/varDatabaseDbHandler.js @@ -1,7 +1,6 @@ // @ts-check const fs = require("fs"); -const mysql = require("mysql"); const parseDbResults = require("./parseDbResults"); const dbHandler = require("./dbHandler"); @@ -22,14 +21,9 @@ module.exports = async function varDatabaseDbHandler({ queryString, queryValuesA * * @description Create Connection */ - const connection = mysql.createConnection({ - host: process.env.DSQL_SOCKET_HOST, - user: process.env.DSQL_SOCKET_USER, - password: process.env.DSQL_SOCKET_PASS || "", - database: process.env.DSQL_SOCKET_DB_NAME, - charset: "utf8mb4", - port: parseInt(process.env.DSQL_SOCKET_DB_NAME || "") || undefined, - }); + + const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; + const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || ""; /** * Declare variables diff --git a/engine/query/utils/runQuery.js b/engine/query/utils/runQuery.js index 52538de..4d26e54 100644 --- a/engine/query/utils/runQuery.js +++ b/engine/query/utils/runQuery.js @@ -43,9 +43,7 @@ async function runQuery({ dbFullName, query, readOnly, dbSchema, queryValuesArra const table = tableName ? tableName : typeof query == "string" ? null : query ? query?.table : null; if (!table) throw new Error("No table name provided"); tableSchema = dbSchema.tables.filter((tb) => tb?.tableName === table)[0]; - } catch (_err) { - // console.log("ERROR getting tableSchema: ", _err.message); - } + } catch (_err) {} } /** diff --git a/engine/user/add-user.js b/engine/user/add-user.js index 3e70a02..b69bba7 100644 --- a/engine/user/add-user.js +++ b/engine/user/add-user.js @@ -86,10 +86,13 @@ async function localAddUser({ payload, dbSchema }) { return { success: false, payload: `${invalidField} is not a valid field!` }; } + const tableSchema = dbSchema.tables.find((tb) => tb?.tableName === "users"); + const existingUser = await varDatabaseDbHandler({ queryString: `SELECT * FROM users WHERE email = ?${payload.username ? "OR username = ?" : ""}}`, queryValuesArray: payload.username ? [payload.email, payload.username] : [payload.email], database: dbFullName, + tableSchema: tableSchema, }); if (existingUser && existingUser[0]) { @@ -111,6 +114,7 @@ async function localAddUser({ payload, dbSchema }) { }, encryptionKey, encryptionSalt, + tableSchema, }); if (addUser?.insertId) { diff --git a/engine/user/get-user.js b/engine/user/get-user.js new file mode 100644 index 0000000..8c934ff --- /dev/null +++ b/engine/user/get-user.js @@ -0,0 +1,53 @@ +// @ts-check + +const varDatabaseDbHandler = require("../engine/utils/varDatabaseDbHandler"); + +/** + * + * @param {object} param0 + * @param {number} param0.userId + * @param {string[]} param0.fields + * @param {import("../../types/database-schema.td").DSQL_DatabaseSchemaType} [param0.dbSchema] + * @returns + */ +async function getLocalUser({ userId, fields, dbSchema }) { + /** + * GRAB user + * + * @description GRAB user + */ + const sanitizedFields = fields.map((fld) => fld.replace(/[^a-z\_]/g, "")); + const query = `SELECT ${sanitizedFields.join(",")} FROM users WHERE id = ?`; + + const tableSchema = dbSchema?.tables.find((tb) => tb?.tableName === "users"); + + let foundUser = await varDatabaseDbHandler({ + queryString: query, + queryValuesArray: [userId.toString()], + database: process.env.DSQL_DB_NAME || "", + tableSchema, + }); + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + if (!foundUser || !foundUser[0]) + return { + success: false, + payload: null, + msg: "User not found!", + }; + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + /** ********************* Send Response */ + return { + success: true, + payload: foundUser[0], + }; +} + +module.exports = getLocalUser; diff --git a/engine/user/login-user.js b/engine/user/login-user.js new file mode 100644 index 0000000..814b09a --- /dev/null +++ b/engine/user/login-user.js @@ -0,0 +1,146 @@ +// @ts-check + +const hashPassword = require("../../functions/hashPassword"); +const varDatabaseDbHandler = require("../engine/utils/varDatabaseDbHandler"); + +/** + * + * @param {object} param0 + * @param {{ + * email?: string, + * username?: string, + * password: string, + * }} param0.payload + * @param {string[]} [param0.additionalFields] + * @param {import("../../types/database-schema.td").DSQL_DatabaseSchemaType} [param0.dbSchema] + * @returns + */ +async function loginLocalUser({ payload, additionalFields, dbSchema }) { + try { + /** + * User auth + * + * @description Authenticate user + */ + + const { email, username, password } = payload; + + const dbFullName = process.env.DSQL_DB_NAME || ""; + + /** + * Check input validity + * + * @description Check input validity + */ + if (email?.match(/ /) || username?.match(/ /) || password?.match(/ /)) { + return { + success: false, + msg: "Invalid Email/Password format", + }; + } + + /** + * Password hash + * + * @description Password hash + */ + let hashedPassword = hashPassword(password); + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + const tableSchema = dbSchema?.tables.find((tb) => tb?.tableName === "users"); + + let foundUser = await varDatabaseDbHandler({ + queryString: `SELECT * FROM users WHERE email = ? OR username = ?`, + queryValuesArray: [email || "", username || ""], + database: dbFullName.replace(/[^a-z0-9_]/g, ""), + tableSchema, + }); + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + if (!foundUser || !foundUser[0]) + return { + success: false, + payload: null, + msg: "No user found", + }; + + let isPasswordCorrect = false; + + if (foundUser && foundUser[0]) { + isPasswordCorrect = hashedPassword === foundUser[0].password; + } + + let socialUserValid = false; + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + if (!isPasswordCorrect && !socialUserValid) { + return { + success: false, + msg: "Wrong password, no social login validity", + payload: null, + }; + } + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + let csrfKey = Math.random().toString(36).substring(2) + "-" + Math.random().toString(36).substring(2); + + let userPayload = { + id: foundUser[0].id, + first_name: foundUser[0].first_name, + last_name: foundUser[0].last_name, + username: foundUser[0].username, + email: foundUser[0].email, + phone: foundUser[0].phone, + social_id: foundUser[0].social_id, + image: foundUser[0].image, + image_thumbnail: foundUser[0].image_thumbnail, + verification_status: foundUser[0].verification_status, + social_login: foundUser[0].social_login, + social_platform: foundUser[0].social_platform, + csrf_k: csrfKey, + more_data: foundUser[0].more_user_data, + logged_in_status: true, + date: Date.now(), + }; + + if (additionalFields && Array.isArray(additionalFields) && additionalFields.length > 0) { + additionalFields.forEach((key) => { + userPayload[key] = foundUser?.[0][key]; + }); + } + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + /** ********************* Send Response */ + return { + success: true, + msg: "Login Successful", + payload: userPayload, + userId: "0", + }; + + //////////////////////////////////////// + } catch (error) { + console.log("Error in local login-user Request =>", error.message); + return { + success: false, + msg: "Login Failed", + }; + } +} + +module.exports = loginLocalUser; diff --git a/engine/user/reauth-user.js b/engine/user/reauth-user.js new file mode 100644 index 0000000..2bd962b --- /dev/null +++ b/engine/user/reauth-user.js @@ -0,0 +1,105 @@ +// @ts-check + +const varDatabaseDbHandler = require("../engine/utils/varDatabaseDbHandler"); + +/** + * + * @param {object} param0 + * @param {*} param0.existingUser + * @param {string[]} [param0.additionalFields] + * @param {import("../../types/database-schema.td").DSQL_DatabaseSchemaType} [param0.dbSchema] + * @returns + */ +async function localReauthUser({ existingUser, additionalFields, dbSchema }) { + try { + /** + * Grab data + * + * @description Grab data + */ + const dbFullName = process.env.DSQL_DB_NAME || ""; + + /** + * GRAB user + * + * @description GRAB user + */ + const tableSchema = dbSchema?.tables.find((tb) => tb?.tableName === "users"); + + let foundUser = + existingUser?.id && existingUser.id.toString().match(/./) + ? await varDatabaseDbHandler({ + queryString: `SELECT * FROM users WHERE id=?`, + queryValuesArray: [existingUser.id], + database: dbFullName.replace(/[^a-z0-9_]/g, ""), + tableSchema, + }) + : null; + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + if (!foundUser || !foundUser[0]) + return { + success: false, + payload: null, + msg: "No user found", + }; + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + let csrfKey = Math.random().toString(36).substring(2) + "-" + Math.random().toString(36).substring(2); + + let userPayload = { + id: foundUser[0].id, + first_name: foundUser[0].first_name, + last_name: foundUser[0].last_name, + username: foundUser[0].username, + email: foundUser[0].email, + phone: foundUser[0].phone, + social_id: foundUser[0].social_id, + image: foundUser[0].image, + image_thumbnail: foundUser[0].image_thumbnail, + verification_status: foundUser[0].verification_status, + social_login: foundUser[0].social_login, + social_platform: foundUser[0].social_platform, + csrf_k: csrfKey, + more_data: foundUser[0].more_user_data, + logged_in_status: true, + date: Date.now(), + }; + + if (additionalFields && Array.isArray(additionalFields) && additionalFields.length > 0) { + additionalFields.forEach((key) => { + userPayload[key] = foundUser?.[0][key]; + }); + } + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + /** ********************* Send Response */ + return { + success: true, + msg: "Login Successful", + payload: userPayload, + userId: "0", + }; + + //////////////////////////////////////// + + //////////////////////////////////////// + } catch (error) { + console.log("Error in local login-user Request =>", error.message); + return { + success: false, + msg: "Login Failed", + }; + } +} + +module.exports = localReauthUser; diff --git a/engine/user/social/github-auth.js b/engine/user/social/github-auth.js new file mode 100644 index 0000000..12d8ad7 --- /dev/null +++ b/engine/user/social/github-auth.js @@ -0,0 +1,133 @@ +// @ts-check + +/** + * ============================================================================== + * Imports + * ============================================================================== + */ +const http = require("http"); +const https = require("https"); +const encrypt = require("../../../functions/encrypt"); +const camelJoinedtoCamelSpace = require("../../engine/utils/camelJoinedtoCamelSpace"); +const githubLogin = require("./utils/githubLogin"); +const handleSocialDb = require("./utils/handleSocialDb"); + +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ + +const database = process.env.DSQL_DB_NAME || ""; +const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; +const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || ""; + +/** + * SERVER FUNCTION: Login with google Function + * ============================================================================== + * + * @async + * + * @param {object} params - main params object + * @param {http.ServerResponse} params.res - HTTPS response object + * @param {string} params.code + * @param {string} [params.email] + * @param {string} params.clientId + * @param {string} params.clientSecret + * @param {string[]} [params.additionalFields] + * @param {import("../../../types/database-schema.td").DSQL_DatabaseSchemaType} params.dbSchema + */ +async function localGithubAuth({ res, code, email, clientId, clientSecret, additionalFields, dbSchema }) { + try { + /** + * User auth + * + * @description Authenticate user + */ + if (!code || !clientId || !clientSecret) { + return { + success: false, + msg: "Missing query params", + }; + } + + if (typeof code !== "string" || typeof clientId !== "string" || typeof clientSecret !== "string" || typeof database !== "string") { + return { + success: false, + msg: "Wrong Parameters", + }; + } + + /** + * Create new user folder and file + * + * @description Create new user folder and file + */ + const gitHubUser = await githubLogin({ + code: code, + clientId: clientId, + clientSecret: clientSecret, + }); + + if (!gitHubUser) { + return { + success: false, + msg: "No github user returned", + }; + } + + const targetDbName = database; + + const socialId = gitHubUser.name || gitHubUser.id || gitHubUser.login; + const targetName = gitHubUser.name || gitHubUser.login; + const nameArray = targetName?.match(/ /) ? targetName?.split(" ") : targetName?.match(/\-/) ? targetName?.split("-") : [targetName]; + + const payload = { + email: gitHubUser.email, + first_name: camelJoinedtoCamelSpace(nameArray[0]) || "", + last_name: camelJoinedtoCamelSpace(nameArray[1]) || "", + social_id: socialId, + social_platform: "github", + image: gitHubUser.avatar_url, + image_thumbnail: gitHubUser.avatar_url, + username: "github-user-" + socialId, + }; + + if (additionalFields && Object.keys(additionalFields).length > 0) { + Object.keys(additionalFields).forEach((key) => { + payload[key] = additionalFields[key]; + }); + } + + const loggedInGithubUser = await handleSocialDb({ + database: targetDbName, + email: gitHubUser.email, + payload: payload, + social_platform: "github", + res: res, + social_id: socialId, + supEmail: email, + additionalFields, + dbSchema: dbSchema, + }); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return { ...loggedInGithubUser, dsqlUserId: "0" }; + + //////////////////////////////////////// + } catch (error) { + console.log("localGithubAuth error", error.message); + + return { success: false, msg: "Failed!" }; + } +} + +//////////////////////////////////////// +//////////////////////////////////////// +//////////////////////////////////////// + +module.exports = localGithubAuth; diff --git a/engine/user/social/google-auth.js b/engine/user/social/google-auth.js new file mode 100644 index 0000000..c628dde --- /dev/null +++ b/engine/user/social/google-auth.js @@ -0,0 +1,161 @@ +// @ts-check + +/** + * ============================================================================== + * Imports + * ============================================================================== + */ +const http = require("http"); +const https = require("https"); +const fs = require("fs"); +const path = require("path"); +const encrypt = require("../../../functions/encrypt"); +const decrypt = require("../../../functions/decrypt"); + +const { OAuth2Client } = require("google-auth-library"); +const handleSocialDb = require("./utils/handleSocialDb"); + +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ + +/** + * @typedef {object | null} FunctionReturn + * @property {boolean} success - Did the function run successfully? + * @property {import("../../types/user.td").DATASQUIREL_LoggedInUser | null} user - Returned User + * @property {number} [dsqlUserId] - Dsql User Id + * @property {string} [msg] - Response message + */ + +const database = process.env.DSQL_DB_NAME || ""; +const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; +const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || ""; + +/** + * SERVER FUNCTION: Login with google Function + * ============================================================================== + * + * @async + * + * @param {object} params - main params object + * @param {string} params.token - Google access token gotten from the client side + * @param {string} params.clientId - Google client id + * @param {http.ServerResponse} params.response - HTTPS response object + * @param {object} [params.additionalFields] - Additional Fields to be added to the user object + * @param {import("../../../types/database-schema.td").DSQL_DatabaseSchemaType} [params.dbSchema] - Database Schema + * + * @returns { Promise } + */ +async function localGoogleAuth({ dbSchema, token, clientId, response, additionalFields }) { + /** + * Send Response + * + * @description Send a boolean response + */ + try { + /** + * Grab User data + * + * @description Grab User data + */ + const client = new OAuth2Client(clientId); + + const ticket = await client.verifyIdToken({ + idToken: token, + audience: clientId, + }); + + if (!ticket?.getPayload()?.email_verified) { + return { + success: false, + user: null, + }; + } + + const payload = ticket.getPayload(); + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + if (!database || typeof database != "string" || database?.match(/ /)) { + return { + success: false, + user: undefined, + msg: "Please provide a database slug(database name in lowercase with no spaces)", + }; + } + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + /** + * Create new user folder and file + * + * @description Create new user folder and file + */ + const targetDbName = database; + + if (!payload) { + return { + success: false, + msg: "No payload", + }; + } + + const { given_name, family_name, email, sub, picture, email_verified } = payload; + + const payloadObject = { + email: email || "", + first_name: given_name || "", + last_name: family_name || "", + social_id: sub, + social_platform: "google", + image: picture || "", + image_thumbnail: picture || "", + username: `google-user-${sub}`, + }; + + if (additionalFields && Object.keys(additionalFields).length > 0) { + Object.keys(additionalFields).forEach((key) => { + payloadObject[key] = additionalFields[key]; + }); + } + + const loggedInGoogleUser = await handleSocialDb({ + database: targetDbName, + email: email || "", + payload: payloadObject, + social_platform: "google", + res: response, + social_id: sub, + additionalFields, + dbSchema, + }); + + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + return { ...loggedInGoogleUser, dsqlUserId: "0" }; + + //////////////////////////////////////// + } catch (error) { + return { + success: false, + msg: "User fetch Error", + }; + + //////////////////////////////////////// + } +} + +//////////////////////////////////////// +//////////////////////////////////////// +//////////////////////////////////////// + +module.exports = localGoogleAuth; diff --git a/engine/user/social/utils/githubLogin.js b/engine/user/social/utils/githubLogin.js new file mode 100644 index 0000000..580392d --- /dev/null +++ b/engine/user/social/utils/githubLogin.js @@ -0,0 +1,160 @@ +// @ts-check + +/** + * ============================================================================== + * Imports + * ============================================================================== + */ +const fs = require("fs"); +const httpsRequest = require("./httpsRequest"); + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +/** + * + * @typedef {object} GithubUserPayload + * @property {string} login - Full name merged eg. "JohnDoe" + * @property {number} id - github user id + * @property {string} node_id - Some other id + * @property {string} avatar_url - profile picture + * @property {string} gravatar_id - some other id + * @property {string} url - Github user URL + * @property {string} html_url - User html URL - whatever that means + * @property {string} followers_url - Followers URL + * @property {string} following_url - Following URL + * @property {string} gists_url - Gists URL + * @property {string} starred_url - Starred URL + * @property {string} subscriptions_url - Subscriptions URL + * @property {string} organizations_url - Organizations URL + * @property {string} repos_url - Repositories URL + * @property {string} received_events_url - Received Events URL + * @property {string} type - Common value => "User" + * @property {boolean} site_admin - Is site admin or not? Boolean + * @property {string} name - More like "username" + * @property {string} company - User company + * @property {string} blog - User blog URL + * @property {string} location - User Location + * @property {string} email - User Email + * @property {string} hireable - Is user hireable + * @property {string} bio - User bio + * @property {string} twitter_username - User twitter username + * @property {number} public_repos - Number of public repositories + * @property {number} public_gists - Number of public gists + * @property {number} followers - Number of followers + * @property {number} following - Number of following + * @property {string} created_at - Date created + * @property {string} updated_at - Date updated + */ + +/** + * Login/signup a github user + * ============================================================================== + * @async + * + * @param {Object} params - foundUser if any + * @param {string} params.code - github auth token + * @param {string} params.clientId - github client Id + * @param {string} params.clientSecret - github client Secret + * + * @returns {Promise} + */ +async function githubLogin({ code, clientId, clientSecret }) { + /** @type {GithubUserPayload | null} */ + let gitHubUser = null; + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + try { + /** + * Create new user folder and file + * + * @description Create new user folder and file + */ + // const response = await fetch(`https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}`); + const response = await httpsRequest({ + method: "POST", + hostname: "github.com", + path: `/login/oauth/access_token?client_id=${clientId}&client_secret=${clientSecret}&code=${code}`, + headers: { + Accept: "application/json", + "User-Agent": "*", + }, + }); + + // `https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}&client_secret=${process.env.GITHUB_SECRET}&code=${code}`, + // body: JSON.stringify({ + // client_id: process.env.GITHUB_ID, + // client_secret: process.env.GITHUB_SECRET, + // code: code, + // }), + + const accessTokenObject = JSON.parse(response); + + if (!accessTokenObject?.access_token) { + return gitHubUser; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + const userDataResponse = await httpsRequest({ + method: "GET", + hostname: "api.github.com", + path: "/user", + headers: { + Authorization: `Bearer ${accessTokenObject.access_token}`, + "User-Agent": "*", + }, + }); + + gitHubUser = JSON.parse(userDataResponse); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + if (!gitHubUser?.email) { + const existingGithubUser = await global.DB_HANDLER(`SELECT email FROM users WHERE social_login='1' AND social_platform='github' AND social_id='${gitHubUser?.id || ""}'`); + + if (existingGithubUser && existingGithubUser[0] && gitHubUser) { + gitHubUser.email = existingGithubUser[0].email; + } + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + } catch (error) { + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + console.log("ERROR in githubLogin.js backend function =>", error.message); + + // serverError({ + // component: "/api/social-login/github-auth/catch-error", + // message: error.message, + // user: user, + // }); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return gitHubUser; +} + +module.exports = githubLogin; diff --git a/engine/user/social/utils/googleLogin.js b/engine/user/social/utils/googleLogin.js new file mode 100644 index 0000000..1081d55 --- /dev/null +++ b/engine/user/social/utils/googleLogin.js @@ -0,0 +1,142 @@ +/** + * ============================================================================== + * Imports + * ============================================================================== + */ +const fs = require("fs"); + +//////////////////////////////////////////////// +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +const { OAuth2Client } = require("google-auth-library"); + +const { hashPassword } = require("../passwordHash"); +const serverError = require("../serverError"); + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +/** + * ============================================================================== + * Main Function + * ============================================================================== + * @param {Object} params - foundUser if any + */ +module.exports = async function googleLogin({ usertype, foundUser, isSocialValidated, isUserValid, reqBody, serverRes, loginFailureReason }) { + const client = new OAuth2Client(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID); + let isGoogleAuthValid = false; + let newFoundUser = null; + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + try { + const ticket = await client.verifyIdToken({ + idToken: reqBody.token, + audience: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend + // Or, if multiple clients access the backend: + //[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3] + }); + + const payload = ticket.payload; + const userid = payload["sub"]; + + isUserValid = payload.email_verified; + + if (!isUserValid || !payload || !payload.email_verified) return; + + serverRes.isUserValid = payload.email_verified; + isSocialValidated = payload.email_verified; + isGoogleAuthValid = payload.email_verified; + ////// If request specified a G Suite domain: + ////// const domain = payload['hd']; + + let socialHashedPassword = hashPassword(payload.jti); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + let existinEmail = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE email='${payload.email}' AND social_login!='1' AND social_platform!='google'`); + + if (existinEmail && existinEmail[0]) { + loginFailureReason = "Email Exists Already"; + isGoogleAuthValid = false; + return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser, loginFailureReason: loginFailureReason }; + } + + //////////////////////////////////////// + + foundUser = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE email='${payload.email}' AND social_login='1' AND social_platform='google'`); + + if (foundUser && foundUser[0]) { + newFoundUser = foundUser; + return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser }; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + let newUser = await global.DB_HANDLER(`INSERT INTO ${usertype} ( + first_name, + last_name, + social_platform, + social_name, + social_id, + email, + image, + image_thumbnail, + password, + verification_status, + social_login, + terms_agreement, + date_created, + date_code + ) VALUES ( + '${payload.given_name}', + '${payload.family_name}', + 'google', + 'google_${payload.email.replace(/@.*/, "")}', + '${payload.sub}', + '${payload.email}', + '${payload.picture}', + '${payload.picture}', + '${socialHashedPassword}', + '1', + '1', + '1', + '${Date()}', + '${Date.now()}' + )`); + + newFoundUser = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE id='${newUser.insertId}'`); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + } catch (error) { + serverError({ + component: "googleLogin", + message: error.message, + user: {}, + }); + + loginFailureReason = error; + + isUserValid = false; + isSocialValidated = false; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser }; +}; diff --git a/engine/user/social/utils/handleSocialDb.js b/engine/user/social/utils/handleSocialDb.js new file mode 100644 index 0000000..f9b46e3 --- /dev/null +++ b/engine/user/social/utils/handleSocialDb.js @@ -0,0 +1,377 @@ +// @ts-check + +/** + * ============================================================================== + * Imports + * ============================================================================== + */ +const fs = require("fs"); +const http = require("http"); +const varDatabaseDbHandler = require("../../../engine/utils/varDatabaseDbHandler"); +const addDbEntry = require("../../../query/utils/addDbEntry"); +const encrypt = require("../../../../functions/encrypt"); + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +/** + * @typedef {object} FunctionReturn + * @property {boolean} success - Did the operation complete successfully or not? + * @property {{ + * id: number, + * first_name: string, + * last_name: string, + * }|null} user - User payload object: or "null" + * @property {string} [msg] - Message + * @property {string} [error] - Error Message + * @property {string | number} [social_id] - Social Id + * @property {string} [social_platform] - Social Platform + * @property {object} [payload] - Payload + * @property {boolean} [alert] - Alert + * @property {*} [newUser] - New User + */ + +//////////////////////////////////////////////// +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +const database = process.env.DSQL_DB_NAME || ""; +const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; +const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || ""; + +//////////////////////////////////////////////// +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +/** + * Handle Social User Auth on Datasquirel Database + * ============================================================================== + * + * @description This function handles all social login logic after the social user + * has been authenticated and userpayload is present. The payload MUST contain the + * specified fields because this funciton will create a new user if the authenticated + * user does not exist. + * + * @param {{ + * database: string|null|undefined, + * social_id: string|number, + * email: string, + * social_platform: string, + * payload: { + * social_id: string | number, + * email: string, + * social_platform: string, + * first_name: string, + * last_name: string, + * image: string, + * image_thumbnail: string, + * username: string, + * }, + * res: object|null, + * invitation?: object|null, + * supEmail?: string | null, + * additionalFields?: object, + * dbSchema: import("../../../../types/database-schema.td").DSQL_DatabaseSchemaType | undefined + * }} params - function parameters inside an object + * + * @returns {Promise} - Response object + */ +async function handleSocialDb({ social_id, email, social_platform, payload, res, invitation, supEmail, additionalFields, dbSchema }) { + const tableSchema = dbSchema?.tables.find((tb) => tb?.tableName === "users"); + + try { + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + let existingSocialIdUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: `SELECT * FROM users WHERE social_id = ? AND social_login='1' AND social_platform = ? `, + queryValuesArray: [social_id.toString(), social_platform], + }); + + if (existingSocialIdUser && existingSocialIdUser[0]) { + return await loginSocialUser({ + user: existingSocialIdUser[0], + social_platform, + res, + invitation, + database, + additionalFields, + }); + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + const finalEmail = email ? email : supEmail ? supEmail : null; + + if (!finalEmail) { + return { + success: false, + user: null, + msg: "No Email Present", + social_id, + social_platform, + payload, + }; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + let existingEmailOnly = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: `SELECT * FROM users WHERE email='${finalEmail}'`, + }); + + if (existingEmailOnly && existingEmailOnly[0]) { + return { + success: false, + user: null, + msg: "This Email is already taken", + alert: true, + }; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + const foundUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: `SELECT * FROM users WHERE email='${finalEmail}' AND social_login='1' AND social_platform='${social_platform}' AND social_id='${social_id}'`, + }); + + if (foundUser && foundUser[0]) { + return await loginSocialUser({ + user: payload, + social_platform, + res, + invitation, + database, + additionalFields, + }); + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + const socialHashedPassword = await encrypt({ + data: social_id.toString(), + encryptionKey, + encryptionSalt, + }); + + const data = { + social_login: "1", + verification_status: supEmail ? "0" : "1", + password: socialHashedPassword, + }; + + Object.keys(payload).forEach((key) => { + data[key] = payload[key]; + }); + + const newUser = await addDbEntry({ + dbFullName: database ? database : "datasquirel", + tableName: "users", + duplicateColumnName: "email", + duplicateColumnValue: finalEmail, + data: { + ...data, + email: finalEmail, + }, + encryptionKey, + encryptionSalt, + tableSchema, + }); + + if (newUser?.insertId) { + const newUserQueried = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: `SELECT * FROM users WHERE id='${newUser.insertId}'`, + }); + + if (!newUserQueried || !newUserQueried[0]) + return { + success: false, + user: null, + msg: "User Insertion Failed!", + }; + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + if (supEmail && database?.match(/^datasquirel$/)) { + /** + * Send email Verification + * + * @description Send verification email to newly created agent + */ + let generatedToken = encrypt({ + data: JSON.stringify({ + id: newUser.insertId, + email: supEmail, + dateCode: Date.now(), + }), + encryptionKey, + encryptionSalt, + }); + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return await loginSocialUser({ + user: newUserQueried[0], + social_platform, + res, + invitation, + database, + additionalFields, + }); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + } else { + console.log("Social User Failed to insert in 'handleSocialDb.js' backend function =>", newUser); + + return { + success: false, + user: null, + msg: "Social User Failed to insert in 'handleSocialDb.js' backend function => ", + newUser: newUser, + }; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + } catch (error) { + console.log("ERROR in 'handleSocialDb.js' backend function =>", error.message); + + return { + success: false, + user: null, + error: error.message, + }; + + // serverError({ + // component: "/functions/backend/social-login/handleSocialDb.js - main-catch-error", + // message: error.message, + // user: { first_name, last_name }, + // }); + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// +} + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +/** + * Function to login social user + * ============================================================================== + * @description This function logs in the user after 'handleSocialDb' function finishes + * the user creation or confirmation process + * + * @async + * + * @param {object} params - function parameters inside an object + * @param {{ + * first_name: string, + * last_name: string, + * email: string, + * social_id: string|number, + * }} params.user - user object + * @param {string} params.social_platform - Whether its "google" or "facebook" or "github" + * @param {http.ServerResponse} params.res - Https response object + * @param {object} params.invitation - A query object if user was invited + * @param {string|null} params.database - Target Database + * @param {object} [params.additionalFields] - Additional fields to be added to the user payload + * + * @returns {Promise<{ + * success: boolean, + * user: { id: number, first_name: string, last_name: string } | null + * msg?: string + * }>} + */ +async function loginSocialUser({ user, social_platform, res, invitation, database, additionalFields }) { + const foundUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: `SELECT * FROM users WHERE email='${user.email}' AND social_id='${user.social_id}' AND social_platform='${social_platform}'`, + }); + + let csrfKey = Math.random().toString(36).substring(2) + "-" + Math.random().toString(36).substring(2); + + if (!foundUser?.[0]) { + return { + success: false, + user: null, + msg: "User Not Found", + }; + } + + let userPayload = { + id: foundUser[0].id, + type: foundUser[0].type || "", + stripe_id: foundUser[0].stripe_id || "", + first_name: foundUser[0].first_name, + last_name: foundUser[0].last_name, + username: foundUser[0].username, + email: foundUser[0].email, + social_id: foundUser[0].social_id, + image: foundUser[0].image, + image_thumbnail: foundUser[0].image_thumbnail, + verification_status: foundUser[0].verification_status, + social_login: foundUser[0].social_login, + social_platform: foundUser[0].social_platform, + csrf_k: csrfKey, + logged_in_status: true, + date: Date.now(), + }; + + if (additionalFields && Object.keys(additionalFields).length > 0) { + Object.keys(additionalFields).forEach((key) => { + userPayload[key] = foundUser[0][key]; + }); + } + + let encryptedPayload = encrypt({ + data: JSON.stringify(userPayload), + encryptionKey, + encryptionSalt, + }); + + if (res?.setHeader) { + res.setHeader("Set-Cookie", [`datasquirelAuthKey=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, `csrf=${csrfKey};samesite=strict;path=/;HttpOnly=true`]); + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return { + success: true, + user: userPayload, + }; +} + +module.exports = handleSocialDb; diff --git a/engine/user/social/utils/httpsRequest.js b/engine/user/social/utils/httpsRequest.js new file mode 100644 index 0000000..c32e0e7 --- /dev/null +++ b/engine/user/social/utils/httpsRequest.js @@ -0,0 +1,107 @@ +// @ts-check + +/** + * Imports + * ============================================================================== + */ +const https = require("https"); + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +/** + * Main Function + * ============================================================================== + * @param {{ + * url?: string, + * method: string, + * hostname: string, + * path?: string, + * href?: string, + * headers?: object, + * body?: object, + * }} params - params + */ +function httpsRequest({ url, method, hostname, path, href, headers, body }) { + const reqPayloadString = body ? JSON.stringify(body) : null; + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + let requestOptions = { + method: method, + hostname: hostname, + port: 443, + headers: {}, + }; + + if (path) requestOptions.path = path; + if (href) requestOptions.href = href; + + if (headers) requestOptions.headers = headers; + if (body) { + requestOptions.headers["Content-Type"] = "application/json"; + requestOptions.headers["Content-Length"] = Buffer.from(reqPayloadString || "").length; + } + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + return new Promise((res, rej) => { + const httpsRequest = https.request( + /* ====== Request Options object ====== */ + // @ts-ignore + url ? url : requestOptions, + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + + /* ====== Callback function ====== */ + (response) => { + var str = ""; + + // ## another chunk of data has been received, so append it to `str` + response.on("data", function (chunk) { + str += chunk; + }); + + // ## the whole response has been received, so we just print it out here + response.on("end", function () { + res(str); + }); + + response.on("error", (error) => { + console.log("HTTP response error =>", error.message); + }); + } + ); + + if (body) httpsRequest.write(reqPayloadString); + + httpsRequest.on("error", (error) => { + console.log("HTTPS request ERROR =>", error); + }); + + httpsRequest.end(); + + //////////////////////////////////////////////// + //////////////////////////////////////////////// + //////////////////////////////////////////////// + }); +} + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +module.exports = httpsRequest; diff --git a/engine/user/update-user.js b/engine/user/update-user.js new file mode 100644 index 0000000..5b20dc3 --- /dev/null +++ b/engine/user/update-user.js @@ -0,0 +1,89 @@ +// @ts-check + +const hashPassword = require("../../functions/hashPassword"); +const addUsersTableToDb = require("../engine/addUsersTableToDb"); +const varDatabaseDbHandler = require("../engine/utils/varDatabaseDbHandler"); +const addDbEntry = require("../query/utils/addDbEntry"); +const runQuery = require("../query/utils/runQuery"); +const updateDbEntry = require("../query/utils/updateDbEntry"); + +/** + * @typedef {Object} LocalPostReturn + * @property {boolean} success - Did the function run successfully? + * @property {*} [payload] - GET request results + * @property {string} [msg] - Message + * @property {string} [error] - Error Message + */ + +/** + * Make a get request to Datasquirel API + * ============================================================================== + * @async + * + * @param {Object} params - Single object passed + * @param {*} params.payload - SQL Query + * @param {import("../../types/database-schema.td").DSQL_DatabaseSchemaType} params.dbSchema - Name of the table to query + * + * @returns { Promise } - Return Object + */ +async function localUpdateUser({ payload, dbSchema }) { + try { + /** + * User auth + * + * @description Authenticate user + */ + /** + * Initialize Variables + */ + const dbFullName = process.env.DSQL_DB_NAME || ""; + + const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; + const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || ""; + + const data = (() => { + const reqBodyKeys = Object.keys(payload); + const finalData = {}; + + reqBodyKeys.forEach((key) => { + if (key?.match(/^date_|^id$/)) return; + finalData[key] = payload[key]; + }); + + return finalData; + })(); + + const tableSchema = dbSchema.tables.find((tb) => tb?.tableName === "users"); + + const updateUser = await updateDbEntry({ + dbContext: "Dsql User", + paradigm: "Full Access", + dbFullName: dbFullName, + tableName: "users", + identifierColumnName: "id", + identifierValue: payload.id, + data: data, + encryptionKey, + encryptionSalt, + tableSchema, + }); + + return { + success: true, + payload: updateUser, + }; + } catch (error) { + //////////////////////////////////////// + console.log("Error in local add-user Request =>", error.message); + + return { + success: false, + payload: null, + msg: "Something went wrong!", + }; + + //////////////////////////////////////// + } +} + +module.exports = localUpdateUser; diff --git a/functions/decrypt.js b/functions/decrypt.js index f801d44..4317066 100644 --- a/functions/decrypt.js +++ b/functions/decrypt.js @@ -14,7 +14,6 @@ const { Buffer } = require("buffer"); const decrypt = ({ encryptedString, encryptionKey, encryptionSalt }) => { if (!encryptedString?.match(/./)) { console.log("Encrypted string is invalid"); - console.log("Encrypted string =>", encryptedString); return encryptedString; } @@ -40,9 +39,6 @@ const decrypt = ({ encryptedString, encryptionKey, encryptionSalt }) => { return decrypted; } catch (error) { console.log("Error in decrypting =>", error.message); - console.log("encryptedString =>", encryptedString); - console.log("encryptionKey =>", encryptionKey); - console.log("encryptionSalt =>", encryptionSalt); return encryptedString; } }; diff --git a/functions/encrypt.js b/functions/encrypt.js index c68e4ed..7a95893 100644 --- a/functions/encrypt.js +++ b/functions/encrypt.js @@ -3,7 +3,19 @@ const { scryptSync, createCipheriv } = require("crypto"); const { Buffer } = require("buffer"); +/** + * + * @param {object} param0 + * @param {string} param0.data + * @param {string} param0.encryptionKey + * @param {string} param0.encryptionSalt + * @returns {string | null} + */ const encrypt = ({ data, encryptionKey, encryptionSalt }) => { + if (!data?.match(/./)) { + console.log("Encryption string is invalid"); + return data; + } if (!encryptionKey?.match(/.{8,}/)) { console.log("Encryption key is invalid"); return data; @@ -25,7 +37,8 @@ const encrypt = ({ data, encryptionKey, encryptionSalt }) => { encrypted += cipher.final("hex"); return encrypted; } catch (error) { - return null; + console.log("Error in encrypting =>", error.message); + return data; } }; diff --git a/functions/hashPassword.js b/functions/hashPassword.js index c788a9d..5927970 100644 --- a/functions/hashPassword.js +++ b/functions/hashPassword.js @@ -1,3 +1,5 @@ +// @ts-check + const { createHmac } = require("crypto"); /** @@ -6,7 +8,8 @@ const { createHmac } = require("crypto"); * @returns {string} */ module.exports = function hashPassword(password) { - const hmac = createHmac("sha512", process.env.ENCRYPTION_PASSWORD); + const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || ""; + const hmac = createHmac("sha512", encryptionKey); hmac.update(password); let hashed = hmac.digest("base64"); return hashed; diff --git a/package-lock.json b/package-lock.json index ab4a40b..3d80500 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,53 @@ { "name": "datasquirel", - "version": "1.4.8", + "version": "1.6.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "datasquirel", - "version": "1.4.8", + "version": "1.6.8", "license": "ISC", "dependencies": { "dotenv": "^16.3.1", + "google-auth-library": "^9.0.0", "mysql": "^2.18.1" }, "bin": { - "dsql": "bin/dsql" + "dsql-dump": "engine/dump.js", + "dsql-watch": "engine/dsql.js" } }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -24,11 +56,32 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -40,16 +93,150 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/gaxios": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.0.tgz", + "integrity": "sha512-EIHuesZxNyIkUGcTQKQPMICyOpDD/bi+LJIJx+NLsSGmnS7N+xCLRX5bi4e9yAu9AlSZdVq+qlyWWVuTh/483w==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.0.0.tgz", + "integrity": "sha512-Ozxyi23/1Ar51wjUT2RDklK+3HxqDr8TLBNK8rBBFQ7T85iIGnXnVusauj06QyqCXRFZig8LZC+TUddWbndlpQ==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.0.0.tgz", + "integrity": "sha512-IQGjgQoVUAfOk6khqTVMLvWx26R+yPw9uLyb1MNyMQpdKiKt0Fd9sp4NWoINjyGHR8S3iw12hMTYK7O8J07c6Q==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.0.0", + "gcp-metadata": "^6.0.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", + "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/mysql": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", @@ -64,6 +251,25 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -104,10 +310,34 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index b0467b4..c026b74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datasquirel", - "version": "1.6.8", + "version": "1.6.9", "description": "Cloud-based SQL data management tool", "main": "index.js", "bin": { @@ -27,6 +27,7 @@ "homepage": "https://datasquirel.com/", "dependencies": { "dotenv": "^16.3.1", + "google-auth-library": "^9.0.0", "mysql": "^2.18.1" } } diff --git a/users/get-user.js b/users/get-user.js index f217b69..90086ef 100644 --- a/users/get-user.js +++ b/users/get-user.js @@ -4,6 +4,7 @@ * ============================================================================== */ const https = require("https"); +const getLocalUser = require("../engine/user/get-user"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -44,6 +45,19 @@ const https = require("https"); * @returns { Promise} */ async function getUser({ key, userId, database, fields }) { + /** + * Initialize + */ + const defaultFields = ["id", "first_name", "last_name", "email", "username", "image", "image_thumbnail", "verification_status", "date_created", "date_created_code", "date_created_timestamp", "date_updated", "date_updated_code", "date_updated_timestamp"]; + + const updatedFields = fields && fields[0] ? [...defaultFields, ...fields] : defaultFields; + + const reqPayload = JSON.stringify({ + userId, + database, + fields: [...new Set(updatedFields)], + }); + /** * Check for local DB settings * @@ -51,22 +65,32 @@ async function getUser({ key, userId, database, fields }) { */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; + + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} + + console.log("Reading from local database ..."); + + if (dbSchema) { + return await getLocalUser({ + userId, + fields: [...new Set(updatedFields)], + dbSchema, + }); + } + } + /** * Make https request * * @description make a request to datasquirel.com */ const httpResponse = await new Promise((resolve, reject) => { - const defaultFields = ["id", "first_name", "last_name", "email", "username", "image", "image_thumbnail", "verification_status", "date_created", "date_created_code", "date_created_timestamp", "date_updated", "date_updated_code", "date_updated_timestamp"]; - - const updatedFields = fields && fields[0] ? [...defaultFields, ...fields] : defaultFields; - - const reqPayload = JSON.stringify({ - userId, - database, - fields: [...new Set(updatedFields)], - }); - const httpsRequest = https.request( { method: "POST", diff --git a/users/login-user.js b/users/login-user.js index 1bf46b6..a67c461 100644 --- a/users/login-user.js +++ b/users/login-user.js @@ -7,7 +7,10 @@ */ const http = require("http"); const https = require("https"); +const fs = require("fs"); +const path = require("path"); const encrypt = require("../functions/encrypt"); +const loginLocalUser = require("../engine/user/login-user"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -78,6 +81,11 @@ async function loginUser({ key, payload, database, additionalFields, response, e msg: "Encryption Salt must be at least 8 characters", }; + /** + * Initialize HTTP response variable + */ + let httpResponse; + /** * Check for local DB settings * @@ -85,58 +93,78 @@ async function loginUser({ key, payload, database, additionalFields, response, e */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; - /** - * Make https request - * - * @description make a request to datasquirel.com - * - * @type {{ success: boolean, payload: import("../types/user.td").DATASQUIREL_LoggedInUser | null, userId?: number, msg?: string }} - */ - const httpResponse = await new Promise((resolve, reject) => { - const reqPayload = JSON.stringify({ - payload, - database, - additionalFields, - }); + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; - const httpsRequest = https.request( - { - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.from(reqPayload).length, - Authorization: key, + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} + + console.log("Reading from local database ..."); + + if (dbSchema) { + httpResponse = await loginLocalUser({ + payload, + additionalFields, + dbSchema, + }); + } + } else { + /** + * Make https request + * + * @description make a request to datasquirel.com + * + * @type {{ success: boolean, payload: import("../types/user.td").DATASQUIREL_LoggedInUser | null, userId?: number, msg?: string }} + */ + httpResponse = await new Promise((resolve, reject) => { + const reqPayload = JSON.stringify({ + payload, + database, + additionalFields, + }); + + const httpsRequest = https.request( + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.from(reqPayload).length, + Authorization: key, + }, + port: 443, + hostname: "datasquirel.com", + path: `/api/user/login-user`, }, - port: 443, - hostname: "datasquirel.com", - path: `/api/user/login-user`, - }, - /** - * Callback Function - * - * @description https request callback - */ - (response) => { - var str = ""; + /** + * Callback Function + * + * @description https request callback + */ + (response) => { + var str = ""; - response.on("data", function (chunk) { - str += chunk; - }); + response.on("data", function (chunk) { + str += chunk; + }); - response.on("end", function () { - resolve(JSON.parse(str)); - }); + response.on("end", function () { + resolve(JSON.parse(str)); + }); - response.on("error", (err) => { - reject(err); - }); - } - ); + response.on("error", (err) => { + reject(err); + }); + } + ); - httpsRequest.write(reqPayload); - httpsRequest.end(); - }); + httpsRequest.write(reqPayload); + httpsRequest.end(); + }); + } /** ********************************************** */ /** ********************************************** */ diff --git a/users/reauth-user.js b/users/reauth-user.js index 562818b..865a549 100644 --- a/users/reauth-user.js +++ b/users/reauth-user.js @@ -7,9 +7,12 @@ */ const http = require("http"); const https = require("https"); +const fs = require("fs"); +const path = require("path"); const encrypt = require("../functions/encrypt"); const userAuth = require("./user-auth"); +const localReauthUser = require("../engine/user/reauth-user"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -66,6 +69,11 @@ async function reauthUser({ key, database, response, request, level, encryptionK }; } + /** + * Initialize HTTP response variable + */ + let httpResponse; + /** * Check for local DB settings * @@ -73,56 +81,76 @@ async function reauthUser({ key, database, response, request, level, encryptionK */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; - /** - * Make https request - * - * @description make a request to datasquirel.com - */ - const httpResponse = await new Promise((resolve, reject) => { - const reqPayload = JSON.stringify({ - existingUser: existingUser.payload, - database, - additionalFields, - }); + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; - const httpsRequest = https.request( - { - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.from(reqPayload).length, - Authorization: key, + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} + + console.log("Reading from local database ..."); + + if (dbSchema) { + httpResponse = await localReauthUser({ + existingUser: existingUser.payload, + additionalFields, + dbSchema, + }); + } + } else { + /** + * Make https request + * + * @description make a request to datasquirel.com + */ + httpResponse = await new Promise((resolve, reject) => { + const reqPayload = JSON.stringify({ + existingUser: existingUser.payload, + database, + additionalFields, + }); + + const httpsRequest = https.request( + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.from(reqPayload).length, + Authorization: key, + }, + port: 443, + hostname: "datasquirel.com", + path: `/api/user/reauth-user`, }, - port: 443, - hostname: "datasquirel.com", - path: `/api/user/reauth-user`, - }, - /** - * Callback Function - * - * @description https request callback - */ - (response) => { - var str = ""; + /** + * Callback Function + * + * @description https request callback + */ + (response) => { + var str = ""; - response.on("data", function (chunk) { - str += chunk; - }); + response.on("data", function (chunk) { + str += chunk; + }); - response.on("end", function () { - resolve(JSON.parse(str)); - }); + response.on("end", function () { + resolve(JSON.parse(str)); + }); - response.on("error", (err) => { - reject(err); - }); - } - ); + response.on("error", (err) => { + reject(err); + }); + } + ); - httpsRequest.write(reqPayload); - httpsRequest.end(); - }); + httpsRequest.write(reqPayload); + httpsRequest.end(); + }); + } /** ********************************************** */ /** ********************************************** */ diff --git a/users/social/github-auth.js b/users/social/github-auth.js index 2e90922..3cd50a9 100644 --- a/users/social/github-auth.js +++ b/users/social/github-auth.js @@ -7,7 +7,10 @@ */ const http = require("http"); const https = require("https"); +const fs = require("fs"); +const path = require("path"); const encrypt = require("../../functions/encrypt"); +const localGithubAuth = require("../../engine/user/social/github-auth"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -42,7 +45,7 @@ const encrypt = require("../../functions/encrypt"); * @param {string} params.encryptionSalt - Encryption salt * @param {object} [params.additionalFields] - Additional Fields to be added to the user object * - * @returns { Promise } + * @returns { Promise } */ async function githubAuth({ key, code, email, database, clientId, clientSecret, response, encryptionKey, encryptionSalt, additionalFields }) { /** @@ -110,6 +113,11 @@ async function githubAuth({ key, code, email, database, clientId, clientSecret, //////////////////////////////////////// //////////////////////////////////////// + /** + * Initialize HTTP response variable + */ + let httpResponse; + /** * Check for local DB settings * @@ -117,73 +125,93 @@ async function githubAuth({ key, code, email, database, clientId, clientSecret, */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; - /** - * Make https request - * - * @description make a request to datasquirel.com - * @type {FunctionReturn} - Https response object - */ - const httpResponse = await new Promise((resolve, reject) => { - const reqPayload = JSON.stringify({ - code, - email, - clientId, - clientSecret, - database, - additionalFields, - }); + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} - const httpsRequest = https.request( - { - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.from(reqPayload).length, - Authorization: key, + console.log("Reading from local database ..."); + + if (dbSchema) { + httpResponse = await localGithubAuth({ + dbSchema: dbSchema, + code, + email: email || undefined, + clientId, + clientSecret, + additionalFields, + res: response, + }); + } + } else { + /** + * Make https request + * + * @description make a request to datasquirel.com + * @type {FunctionReturn} - Https response object + */ + httpResponse = await new Promise((resolve, reject) => { + const reqPayload = JSON.stringify({ + code, + email, + clientId, + clientSecret, + database, + additionalFields, + }); + + const httpsRequest = https.request( + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.from(reqPayload).length, + Authorization: key, + }, + port: 443, + hostname: "datasquirel.com", + path: `/api/user/github-login`, }, - port: 443, - hostname: "datasquirel.com", - path: `/api/user/github-login`, - }, - /** - * Callback Function - * - * @description https request callback - */ - (response) => { - var str = ""; + /** + * Callback Function + * + * @description https request callback + */ + (response) => { + var str = ""; - response.on("data", function (chunk) { - str += chunk; - }); + response.on("data", function (chunk) { + str += chunk; + }); - response.on("end", function () { - try { - resolve(JSON.parse(str)); - } catch (error) { - console.log(error); + response.on("end", function () { + try { + resolve(JSON.parse(str)); + } catch (error) { + console.log(error); - resolve({ - success: false, - user: null, - msg: "Something went wrong", - }); - } - }); + resolve({ + success: false, + user: null, + msg: "Something went wrong", + }); + } + }); - response.on("error", (err) => { - reject(err); - }); - } - ); - httpsRequest.write(reqPayload); - httpsRequest.end(); - }); + response.on("error", (err) => { + reject(err); + }); + } + ); + httpsRequest.write(reqPayload); + httpsRequest.end(); + }); + } //////////////////////////////////////// //////////////////////////////////////// diff --git a/users/social/google-auth.js b/users/social/google-auth.js index d4a74b6..4e9e7e0 100644 --- a/users/social/google-auth.js +++ b/users/social/google-auth.js @@ -7,7 +7,10 @@ */ const http = require("http"); const https = require("https"); +const fs = require("fs"); +const path = require("path"); const encrypt = require("../../functions/encrypt"); +const localGoogleAuth = require("../../engine/user/social/google-auth"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -108,6 +111,11 @@ async function googleAuth({ key, token, database, clientId, response, encryption //////////////////////////////////////// //////////////////////////////////////// + /** + * Initialize HTTP response variable + */ + let httpResponse; + /** * Check for local DB settings * @@ -115,61 +123,83 @@ async function googleAuth({ key, token, database, clientId, response, encryption */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; - /** - * Make https request - * - * @description make a request to datasquirel.com - * @type {{ success: boolean, user: import("../../types/user.td").DATASQUIREL_LoggedInUser | null, msg?: string, dsqlUserId?: number } | null } - Https response object - */ - const httpResponse = await new Promise((resolve, reject) => { - const reqPayload = JSON.stringify({ - token, - clientId, - database, - additionalFields, - }); + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} - const httpsRequest = https.request( - { - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.from(reqPayload).length, - Authorization: key, + console.log("Reading from local database ..."); + + if (dbSchema) { + httpResponse = await localGoogleAuth({ + dbSchema: dbSchema, + token, + clientId, + additionalFields, + response: response, + }); + } + } else { + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + /** + * Make https request + * + * @description make a request to datasquirel.com + * @type {{ success: boolean, user: import("../../types/user.td").DATASQUIREL_LoggedInUser | null, msg?: string, dsqlUserId?: number } | null } - Https response object + */ + httpResponse = await new Promise((resolve, reject) => { + const reqPayload = JSON.stringify({ + token, + clientId, + database, + additionalFields, + }); + + const httpsRequest = https.request( + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.from(reqPayload).length, + Authorization: key, + }, + port: 443, + hostname: "datasquirel.com", + path: `/api/user/google-login`, }, - port: 443, - hostname: "datasquirel.com", - path: `/api/user/google-login`, - }, - /** - * Callback Function - * - * @description https request callback - */ - (response) => { - var str = ""; + /** + * Callback Function + * + * @description https request callback + */ + (response) => { + var str = ""; - response.on("data", function (chunk) { - str += chunk; - }); + response.on("data", function (chunk) { + str += chunk; + }); - response.on("end", function () { - resolve(JSON.parse(str)); - }); + response.on("end", function () { + resolve(JSON.parse(str)); + }); - response.on("error", (err) => { - reject(err); - }); - } - ); - httpsRequest.write(reqPayload); - httpsRequest.end(); - }); + response.on("error", (err) => { + reject(err); + }); + } + ); + httpsRequest.write(reqPayload); + httpsRequest.end(); + }); + } //////////////////////////////////////// //////////////////////////////////////// diff --git a/users/update-user.js b/users/update-user.js index 125268b..8e0d7b0 100644 --- a/users/update-user.js +++ b/users/update-user.js @@ -1,9 +1,14 @@ +// @ts-check + /** * ============================================================================== * Imports * ============================================================================== */ const https = require("https"); +const path = require("path"); +const fs = require("fs"); +const localUpdateUser = require("../engine/user/update-user"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -39,6 +44,25 @@ async function updateUser({ key, payload, database }) { */ const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME, DSQL_KEY, DSQL_REF_DB_NAME, DSQL_FULL_SYNC } = process.env; + if (DSQL_HOST?.match(/./) && DSQL_USER?.match(/./) && DSQL_PASS?.match(/./) && DSQL_DB_NAME?.match(/./)) { + /** @type {import("../types/database-schema.td").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; + + try { + const localDbSchemaPath = path.resolve(process.cwd(), "dsql.schema.json"); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} + + console.log("Reading from local database ..."); + + if (dbSchema) { + return await localUpdateUser({ + dbSchema: dbSchema, + payload: payload, + }); + } + } + /** * Make https request *