diff --git a/client/auth/google/getAccessToken.d.ts b/client/auth/google/getAccessToken.d.ts index ce778d5..f3a4c30 100644 --- a/client/auth/google/getAccessToken.d.ts +++ b/client/auth/google/getAccessToken.d.ts @@ -1,7 +1,19 @@ -declare function _exports({ clientId, element, triggerPrompt, readyStateDispatch, }: { - clientId: string; - element: HTMLElement; - triggerPrompt: boolean; - readyStateDispatch?: () => void; -}): Promise; +declare namespace _exports { + export { GoogleGetAccessTokenFunctionParams }; +} +declare function _exports(params: GoogleGetAccessTokenFunctionParams): Promise; export = _exports; +type GoogleGetAccessTokenFunctionParams = { + /** + * - Google app client ID: {@link https://datasquirel.com/docs} + */ + clientId: string; + /** + * - Whether to trigger Google signing popup or not: {@link https://datasquirel.com/docs} + */ + triggerPrompt?: boolean; + /** + * - React setState Function: sets whether the google login button is ready or not + */ + setLoading?: React.Dispatch>; +}; diff --git a/client/auth/google/getAccessToken.js b/client/auth/google/getAccessToken.js index a6cbb72..9671c07 100644 --- a/client/auth/google/getAccessToken.js +++ b/client/auth/google/getAccessToken.js @@ -1,9 +1,15 @@ -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// +// @ts-check + +/** + * @typedef {object} GoogleGetAccessTokenFunctionParams + * @property {string} clientId - Google app client ID: {@link https://datasquirel.com/docs} + * @property {boolean} [triggerPrompt] - Whether to trigger Google signing popup or not: {@link https://datasquirel.com/docs} + * @property {React.Dispatch>} [setLoading] - React setState Function: sets whether the google login button is ready or not + * + */ + +/** @type {any} */ +let interval; /** * Login with Google Function @@ -12,89 +18,73 @@ * * @async * - * @param {object} params - Single object passed - * @param {string} params.clientId - Google app client ID: {@link https://datasquirel.com/docs} - * @param {HTMLElement} params.element - HTML Element to display google login button - * @param {boolean} params.triggerPrompt - Whether to trigger Google signing popup or not: {@link https://datasquirel.com/docs} - * @param {function(): void} [params.readyStateDispatch] - React setState Function: sets whether the google login button is ready or not + * @requires script "https://accounts.google.com/gsi/client" async script added to head * + * @param {GoogleGetAccessTokenFunctionParams} params - Single object passed + * @returns {Promise} - Return */ -module.exports = async function getAccessToken({ - clientId, - element, - triggerPrompt, - readyStateDispatch, -}) { - /** - * == Initialize - * - * @description Initialize - */ - const googleScript = document.createElement("script"); - googleScript.src = "https://accounts.google.com/gsi/client"; - googleScript.className = "social-script-tag"; +module.exports = async function getAccessToken(params) { + /** @type {any} */ - document.body.appendChild(googleScript); + params.setLoading?.(true); const response = await new Promise((resolve, reject) => { - googleScript.onload = function (e) { + interval = setInterval(() => { + // @ts-ignore + let google = window.google; + if (google) { - if (readyStateDispatch) readyStateDispatch(true); - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - if (element) { - /** - * Handle google credentials response - * ======================================================== - * @param {object} response - Google response with credentials - * @param {string} response.credential - Google access token - */ - function handleCredentialResponse(response) { - resolve(response.credential); - } - - google.accounts.id.initialize({ - client_id: clientId, - callback: handleCredentialResponse, - }); - - google.accounts.id.renderButton(element, { - theme: "outline", - size: "large", - logo_alignment: "center", - }); - } - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - if (triggerPrompt) { - google.accounts.id.prompt( - /** - * Google prompt notification callback - * ======================================================== - * @param {import("../../../types/user.td").GoogleIdentityPromptNotification} notification - Notification object - */ - (notification) => { - notification.isDisplayed(); - } - ); - } + window.clearInterval(interval); + resolve(googleLogin({ ...params, google })); } - }; + }, 500); }); - ////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////// + params.setLoading?.(false); return response; }; + +/** + * # Google Login Function + * + * @param {GoogleGetAccessTokenFunctionParams & { google: any }} params + * @returns + */ +function googleLogin({ google, clientId, setLoading, triggerPrompt }) { + setTimeout(() => { + setLoading?.(false); + }, 3000); + + return new Promise((resolve, reject) => { + /** + * # Callback Function + * @param {import("../../../package-shared/types").GoogleAccessTokenObject} response + */ + function handleCredentialResponse(response) { + resolve(response.access_token); + } + + const googleAuth = google.accounts.oauth2.initTokenClient({ + client_id: clientId, + scope: "email profile", + callback: handleCredentialResponse, + }); + + googleAuth.requestAccessToken(); + + if (triggerPrompt) { + google.accounts.id.prompt(triggerGooglePromptCallback); + } + + /** + * Google prompt notification callback + * ======================================================== + * @param {import("../../../package-shared/types").GoogleIdentityPromptNotification} notification + */ + function triggerGooglePromptCallback(notification) { + console.log(notification); + } + }); +} diff --git a/engine/dsql.js b/engine/dsql.js index dfa6522..2b0ee7b 100644 --- a/engine/dsql.js +++ b/engine/dsql.js @@ -120,7 +120,6 @@ async function run() { ` - ${colors.FgBlue}Info:${colors.Reset} Now generating and mapping databases ...` ); - // deepcode ignore reDOS: await createDbFromSchema({ dbSchemaData: schemaData, }); diff --git a/index.d.ts b/index.d.ts index 243822c..4a46ae3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,6 +7,7 @@ export namespace media { } export namespace user { export { createUser }; + export let deleteUser: typeof import("./users/delete-user"); export { loginUser }; export { sendEmailCode }; export { logoutUser }; @@ -61,9 +62,9 @@ export declare namespace utils { encryptionKey?: string; encryptionSalt?: string; }) => string; - let hashPassword: ({ password, encryptionKey }: { + let hash: ({ password, encryptionKey }: { password: string; - encryptionKey: string; + encryptionKey?: string; }) => string; } } diff --git a/index.js b/index.js index ca91661..5694897 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,7 @@ const trimSql = require("./package-shared/utils/trim-sql"); */ const user = { createUser: createUser, + deleteUser: require("./users/delete-user"), loginUser: loginUser, sendEmailCode: sendEmailCode, logoutUser: logoutUser, @@ -92,7 +93,7 @@ const datasquirel = { crypto: { encrypt: require("./package-shared/functions/dsql/encrypt"), decrypt: require("./package-shared/functions/dsql/decrypt"), - hashPassword: require("./package-shared/functions/dsql/hashPassword"), + hash: require("./package-shared/functions/dsql/hashPassword"), }, }, }; diff --git a/package-shared/data/possibleFields.json b/package-shared/data/possibleFields.json index 02eb7e9..cf31dc4 100755 --- a/package-shared/data/possibleFields.json +++ b/package-shared/data/possibleFields.json @@ -17,5 +17,6 @@ "onUpdate": "CURRENT_TIMESTAMP", "onUpdateLiteral": "CURRENT_TIMESTAMP", "onDelete": "CURRENT_TIMESTAMP", - "onDeleteLiteral": "CURRENT_TIMESTAMP" + "onDeleteLiteral": "CURRENT_TIMESTAMP", + "encrypted": false } diff --git a/package-shared/data/presets/users.json b/package-shared/data/presets/users.json index bfed22e..3da8faf 100755 --- a/package-shared/data/presets/users.json +++ b/package-shared/data/presets/users.json @@ -33,8 +33,7 @@ }, { "fieldName": "password", - "dataType": "VARCHAR(250)", - "notNullValue": true + "dataType": "TEXT" }, { "fieldName": "image", diff --git a/package-shared/functions/api/query/get.js b/package-shared/functions/api/query/get.js index f992669..ca5ce26 100644 --- a/package-shared/functions/api/query/get.js +++ b/package-shared/functions/api/query/get.js @@ -27,10 +27,9 @@ module.exports = async function apiGet({ }) { if ( typeof query == "string" && - (query.match(/^alter|^delete|information_schema|databases|^create/i) || - !query.match(/^select/i)) + query.match(/^alter|^delete|information_schema|databases|^create/i) ) { - return { success: false, msg: "Wrong Input" }; + return { success: false, msg: "Wrong Input." }; } /** diff --git a/package-shared/functions/api/social-login/facebookLogin.js b/package-shared/functions/api/social-login/facebookLogin.js index ff48de4..7ca8996 100755 --- a/package-shared/functions/api/social-login/facebookLogin.js +++ b/package-shared/functions/api/social-login/facebookLogin.js @@ -8,8 +8,8 @@ const DB_HANDLER = require("../../../utils/backend/global-db/DB_HANDLER"); const handleNodemailer = require("../../backend/handleNodemailer"); -const { hashPassword } = require("../../backend/passwordHash"); const serverError = require("../../backend/serverError"); +const hashPassword = require("../../dsql/hashPassword"); ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// @@ -44,7 +44,9 @@ module.exports = async function facebookLogin({ usertype, body }) { //////////////////////////////////////////////// //////////////////////////////////////////////// - let socialHashedPassword = hashPassword(body.facebookUserId); + let socialHashedPassword = hashPassword({ + password: body.facebookUserId, + }); let newUser = await DB_HANDLER(`INSERT INTO ${usertype} ( first_name, diff --git a/package-shared/functions/api/social-login/googleLogin.js b/package-shared/functions/api/social-login/googleLogin.js index ceee95b..bd4d57a 100755 --- a/package-shared/functions/api/social-login/googleLogin.js +++ b/package-shared/functions/api/social-login/googleLogin.js @@ -13,10 +13,10 @@ const fs = require("fs"); const { OAuth2Client } = require("google-auth-library"); -const { hashPassword } = require("../../backend/passwordHash"); const serverError = require("../../backend/serverError"); const { ServerResponse } = require("http"); const DB_HANDLER = require("../../../utils/backend/global-db/DB_HANDLER"); +const hashPassword = require("../../dsql/hashPassword"); ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// @@ -81,7 +81,9 @@ module.exports = async function googleLogin({ ////// If request specified a G Suite domain: ////// const domain = payload['hd']; - let socialHashedPassword = hashPassword(payload.at_hash || ""); + let socialHashedPassword = hashPassword({ + password: payload.at_hash || "", + }); //////////////////////////////////////////////// //////////////////////////////////////////////// diff --git a/package-shared/functions/api/social-login/handleSocialDb.d.ts b/package-shared/functions/api/social-login/handleSocialDb.d.ts index ff1bae8..31dd893 100644 --- a/package-shared/functions/api/social-login/handleSocialDb.d.ts +++ b/package-shared/functions/api/social-login/handleSocialDb.d.ts @@ -1,19 +1,2 @@ -declare namespace _exports { - export { FunctionReturn }; -} declare const _exports: import("../../../types").HandleSocialDbFunction; export = _exports; -type FunctionReturn = { - /** - * - Did the operation complete successfully or not? - */ - success: boolean; - /** - * - User payload object: or "null" - */ - user: { - id: number; - first_name: string; - last_name: string; - } | null; -}; diff --git a/package-shared/functions/api/social-login/handleSocialDb.js b/package-shared/functions/api/social-login/handleSocialDb.js index 10c8655..5db76a7 100755 --- a/package-shared/functions/api/social-login/handleSocialDb.js +++ b/package-shared/functions/api/social-login/handleSocialDb.js @@ -1,43 +1,13 @@ // @ts-check -/** - * ============================================================================== - * Imports - * ============================================================================== - */ const fs = require("fs"); - -//////////////////////////////////////////////// -//////////////////////////////////////////////// -//////////////////////////////////////////////// - -const addAdminUserOnLogin = require("../../backend/addAdminUserOnLogin"); const handleNodemailer = require("../../backend/handleNodemailer"); -const { ServerResponse } = require("http"); const path = require("path"); const addMariadbUser = require("../../backend/addMariadbUser"); const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); const encrypt = require("../../dsql/encrypt"); const addDbEntry = require("../../backend/db/addDbEntry"); -const getAuthCookieNames = require("../../backend/cookies/get-auth-cookie-names"); -const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); - -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// - -/** - * @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" - */ +const loginSocialUser = require("./loginSocialUser"); /** * @type {import("../../../types").HandleSocialDbFunction} @@ -48,43 +18,29 @@ module.exports = async function handleSocialDb({ email, social_platform, payload, - res, invitation, supEmail, additionalFields, useLocal, }) { - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - try { - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - const existingSocialIdUserQuery = `SELECT * FROM users WHERE social_id = ? AND social_login='1' AND social_platform = ? `; const existingSocialIdUserValues = [ social_id.toString(), social_platform, ]; - let existingSocialIdUser = useLocal - ? await LOCAL_DB_HANDLER( - existingSocialIdUserQuery, - existingSocialIdUserValues - ) - : await varDatabaseDbHandler({ - database: database ? database : "datasquirel", - queryString: existingSocialIdUserQuery, - queryValuesArray: existingSocialIdUserValues, - }); + let existingSocialIdUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: existingSocialIdUserQuery, + queryValuesArray: existingSocialIdUserValues, + useLocal, + }); if (existingSocialIdUser && existingSocialIdUser[0]) { return await loginSocialUser({ user: existingSocialIdUser[0], social_platform, - res, invitation, database, additionalFields, @@ -92,63 +48,46 @@ module.exports = async function handleSocialDb({ }); } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - const finalEmail = email ? email : supEmail ? supEmail : null; if (!finalEmail) { return { success: false, - user: null, + payload: null, msg: "No Email Present", - social_id, - social_platform, - payload, }; } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - const existingEmailOnlyQuery = `SELECT * FROM users WHERE email='${finalEmail}'`; - let existingEmailOnly = useLocal - ? await LOCAL_DB_HANDLER(existingEmailOnlyQuery) - : await varDatabaseDbHandler({ - database: database ? database : "datasquirel", - queryString: existingEmailOnlyQuery, - }); + let existingEmailOnly = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: existingEmailOnlyQuery, + useLocal, + }); if (existingEmailOnly && existingEmailOnly[0]) { return { success: false, - user: null, + payload: null, msg: "This Email is already taken", - alert: true, }; } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// + const foundUserQuery = `SELECT * FROM users WHERE email=? AND social_login='1' AND social_platform=? AND social_id=?`; + const foundUserQueryValues = [finalEmail, social_platform, social_id]; - const foundUserQuery = `SELECT * FROM users WHERE email='${finalEmail}' AND social_login='1' AND social_platform='${social_platform}' AND social_id='${social_id}'`; - - const foundUser = useLocal - ? await LOCAL_DB_HANDLER(foundUserQuery) - : await varDatabaseDbHandler({ - database: database ? database : "datasquirel", - queryString: foundUserQuery, - }); + const foundUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: foundUserQuery, + queryValuesArray: foundUserQueryValues, + useLocal, + }); if (foundUser && foundUser[0]) { return await loginSocialUser({ user: payload, social_platform, - res, invitation, database, additionalFields, @@ -156,10 +95,6 @@ module.exports = async function handleSocialDb({ }); } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - const socialHashedPassword = encrypt({ data: social_id.toString(), }); @@ -200,24 +135,19 @@ module.exports = async function handleSocialDb({ const newUserQueriedQuery = `SELECT * FROM users WHERE id='${newUser.insertId}'`; - const newUserQueried = useLocal - ? await LOCAL_DB_HANDLER(newUserQueriedQuery) - : await varDatabaseDbHandler({ - database: database ? database : "datasquirel", - queryString: newUserQueriedQuery, - }); + const newUserQueried = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: newUserQueriedQuery, + useLocal, + }); if (!newUserQueried || !newUserQueried[0]) return { success: false, - user: null, + payload: null, msg: "User Insertion Failed!", }; - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - if (supEmail && database?.match(/^datasquirel$/)) { /** * Send email Verification @@ -246,15 +176,15 @@ module.exports = async function handleSocialDb({ }).then((mail) => {}); } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - const STATIC_ROOT = process.env.DSQL_STATIC_SERVER_DIR; if (!STATIC_ROOT) { console.log("Static File ENV not Found!"); - return null; + return { + success: false, + payload: null, + msg: "Static File ENV not Found!", + }; } /** @@ -280,23 +210,14 @@ module.exports = async function handleSocialDb({ ); } - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - return await loginSocialUser({ user: newUserQueried[0], social_platform, - res, invitation, database, additionalFields, useLocal, }); - - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// } else { console.log( "Social User Failed to insert in 'handleSocialDb.js' backend function =>", @@ -305,15 +226,10 @@ module.exports = async function handleSocialDb({ return { success: false, - user: null, - msg: "Social User Failed to insert in 'handleSocialDb.js' backend function => ", - newUser: newUser, + payload: null, + msg: "Social User Failed to insert in 'handleSocialDb.js' backend function", }; } - - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// } catch (/** @type {any} */ error) { console.log( "ERROR in 'handleSocialDb.js' backend function =>", @@ -322,126 +238,8 @@ module.exports = async function handleSocialDb({ return { success: false, - user: null, - error: error.message, + payload: null, + msg: error.message, }; } }; - -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////// - -/** - * 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 {ServerResponse} [params.res] - Https response object - * @param {any} [params.invitation] - A query object if user was invited - * @param {string} [params.database] - Target Database - * @param {object} [params.additionalFields] - Additional fields to be added to the user payload - * @param {boolean} [params.useLocal] - * - * @returns {Promise} - */ -async function loginSocialUser({ - user, - social_platform, - res, - invitation, - database, - additionalFields, - useLocal, -}) { - const foundUserQuery = `SELECT * FROM users WHERE email='${user.email}' AND social_id='${user.social_id}' AND social_platform='${social_platform}'`; - - const foundUser = useLocal - ? await LOCAL_DB_HANDLER(foundUserQuery) - : await varDatabaseDbHandler({ - database: database ? database : "datasquirel", - queryString: foundUserQuery, - }); - - if (!foundUser?.[0]) - return { - success: false, - user: null, - }; - - let csrfKey = - Math.random().toString(36).substring(2) + - "-" + - Math.random().toString(36).substring(2); - - /** @type {any} */ - 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) }); - - const { keyCookieName, csrfCookieName } = getAuthCookieNames(); - - if (res?.setHeader) { - res.setHeader("Set-Cookie", [ - `${keyCookieName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, - `${csrfCookieName}=${csrfKey};samesite=strict;path=/;HttpOnly=true`, - ]); - } - - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - - if (invitation && (!database || database?.match(/^datasquirel$/))) { - addAdminUserOnLogin({ - query: invitation, - user: userPayload, - }); - } - - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// - - return { - success: true, - user: userPayload, - }; -} diff --git a/package-shared/functions/api/social-login/loginSocialUser.d.ts b/package-shared/functions/api/social-login/loginSocialUser.d.ts new file mode 100644 index 0000000..a4b06c4 --- /dev/null +++ b/package-shared/functions/api/social-login/loginSocialUser.d.ts @@ -0,0 +1,37 @@ +export = loginSocialUser; +/** + * 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 {any} [params.invitation] - A query object if user was invited + * @param {string} [params.database] - Target Database + * @param {string[]} [params.additionalFields] - Additional fields to be added to the user payload + * @param {boolean} [params.useLocal] + * + * @returns {Promise} + */ +declare function loginSocialUser({ user, social_platform, invitation, database, additionalFields, useLocal, }: { + user: { + first_name: string; + last_name: string; + email: string; + social_id: string | number; + }; + social_platform: string; + invitation?: any; + database?: string; + additionalFields?: string[]; + useLocal?: boolean; +}): Promise; diff --git a/package-shared/functions/api/social-login/loginSocialUser.js b/package-shared/functions/api/social-login/loginSocialUser.js new file mode 100755 index 0000000..839db5f --- /dev/null +++ b/package-shared/functions/api/social-login/loginSocialUser.js @@ -0,0 +1,104 @@ +// @ts-check + +const addAdminUserOnLogin = require("../../backend/addAdminUserOnLogin"); +const { ServerResponse } = require("http"); +const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); +const encrypt = require("../../dsql/encrypt"); +const getAuthCookieNames = require("../../backend/cookies/get-auth-cookie-names"); + +/** + * 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 {any} [params.invitation] - A query object if user was invited + * @param {string} [params.database] - Target Database + * @param {string[]} [params.additionalFields] - Additional fields to be added to the user payload + * @param {boolean} [params.useLocal] + * + * @returns {Promise} + */ +async function loginSocialUser({ + user, + social_platform, + invitation, + database, + additionalFields, + useLocal, +}) { + const foundUserQuery = `SELECT * FROM users WHERE email=? AND social_id=? AND social_platform=?`; + const foundUserValues = [user.email, user.social_id, social_platform]; + + const foundUser = await varDatabaseDbHandler({ + database: database ? database : "datasquirel", + queryString: foundUserQuery, + queryValuesArray: foundUserValues, + useLocal, + }); + + if (!foundUser?.[0]) + return { + success: false, + payload: null, + }; + + let csrfKey = + Math.random().toString(36).substring(2) + + "-" + + Math.random().toString(36).substring(2); + + /** @type {import("../../../types").DATASQUIREL_LoggedInUser} */ + let userPayload = { + id: foundUser[0].id, + first_name: foundUser[0].first_name, + last_name: foundUser[0].last_name, + username: foundUser[0].username, + user_type: foundUser[0].user_type, + 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?.[0]) { + additionalFields.forEach((key) => { + userPayload[key] = foundUser[0][key]; + }); + } + + if (invitation && (!database || database?.match(/^datasquirel$/))) { + addAdminUserOnLogin({ + query: invitation, + user: userPayload, + useLocal, + }); + } + + /** @type {import("../../../types").APILoginFunctionReturn} */ + let result = { + success: true, + payload: userPayload, + csrf: csrfKey, + }; + + return result; +} + +module.exports = loginSocialUser; diff --git a/package-shared/functions/api/users/api-create-user.js b/package-shared/functions/api/users/api-create-user.js index 6bff8be..2580d5d 100644 --- a/package-shared/functions/api/users/api-create-user.js +++ b/package-shared/functions/api/users/api-create-user.js @@ -1,8 +1,8 @@ // @ts-check -const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); const addUsersTableToDb = require("../../backend/addUsersTableToDb"); const addDbEntry = require("../../backend/db/addDbEntry"); +const updateUsersTableSchema = require("../../backend/updateUsersTableSchema"); const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); const hashPassword = require("../../dsql/hashPassword"); @@ -42,27 +42,30 @@ module.exports = async function apiCreateUser({ payload.password = hashedPassword; - let fields = useLocal - ? await LOCAL_DB_HANDLER(`SHOW COLUMNS FROM users`) - : await varDatabaseDbHandler({ - queryString: `SHOW COLUMNS FROM users`, - database: dbFullName, - }); + const fieldsQuery = `SHOW COLUMNS FROM users`; - if (!fields) { + let fields = await varDatabaseDbHandler({ + queryString: fieldsQuery, + database: dbFullName, + useLocal, + }); + + if (!fields?.[0]) { const newTable = await addUsersTableToDb({ userId: Number(userId), - database: database, + database: dbFullName, useLocal, + payload: payload, }); fields = await varDatabaseDbHandler({ - queryString: `SHOW COLUMNS FROM users`, + queryString: fieldsQuery, database: dbFullName, + useLocal, }); } - if (!fields) { + if (!fields?.[0]) { return { success: false, msg: "Could not create users table", @@ -78,8 +81,13 @@ module.exports = async function apiCreateUser({ for (let i = 0; i < Object.keys(payload).length; i++) { const key = Object.keys(payload)[i]; if (!fieldsTitles.includes(key)) { - invalidField = key; - break; + await updateUsersTableSchema({ + userId: Number(userId), + database: dbFullName, + newPayload: { + [key]: payload[key], + }, + }); } } @@ -90,14 +98,18 @@ module.exports = async function apiCreateUser({ }; } + const existingUserQuery = `SELECT * FROM users WHERE email = ?${ + payload.username ? " OR username = ?" : "" + }`; + const existingUserValues = payload.username + ? [payload.email, payload.username] + : [payload.email]; + const existingUser = await varDatabaseDbHandler({ - queryString: `SELECT * FROM users WHERE email = ?${ - payload.username ? " OR username = ?" : "" - }`, - queryValuesArray: payload.username - ? [payload.email, payload.username] - : [payload.email], + queryString: existingUserQuery, + queryValuesArray: existingUserValues, database: dbFullName, + useLocal, }); if (existingUser?.[0]) { @@ -121,9 +133,12 @@ module.exports = async function apiCreateUser({ }); if (addUser?.insertId) { + const newlyAddedUserQuery = `SELECT id,first_name,last_name,email,username,phone,image,image_thumbnail,city,state,country,zip_code,address,verification_status,more_user_data FROM users WHERE id='${addUser.insertId}'`; + const newlyAddedUser = await varDatabaseDbHandler({ - queryString: `SELECT id,first_name,last_name,email,username,phone,image,image_thumbnail,city,state,country,zip_code,address,verification_status,more_user_data FROM users WHERE id='${addUser.insertId}'`, + queryString: newlyAddedUserQuery, database: dbFullName, + useLocal, }); return { diff --git a/package-shared/functions/api/users/api-delete-user.d.ts b/package-shared/functions/api/users/api-delete-user.d.ts new file mode 100644 index 0000000..21e432c --- /dev/null +++ b/package-shared/functions/api/users/api-delete-user.d.ts @@ -0,0 +1,10 @@ +declare function _exports({ dbFullName, deletedUserId, useLocal, }: { + dbFullName: string; + deletedUserId: string | number; + useLocal?: boolean; +}): Promise<{ + success: boolean; + result?: any; + msg?: string; +}>; +export = _exports; diff --git a/package-shared/functions/api/users/api-delete-user.js b/package-shared/functions/api/users/api-delete-user.js new file mode 100644 index 0000000..b240daa --- /dev/null +++ b/package-shared/functions/api/users/api-delete-user.js @@ -0,0 +1,52 @@ +// @ts-check + +const deleteDbEntry = require("../../backend/db/deleteDbEntry"); +const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); + +/** + * # Update API User Function + * + * @param {object} params + * @param {string} params.dbFullName + * @param {string | number} params.deletedUserId + * @param {boolean} [params.useLocal] + * + * @returns {Promise<{ success: boolean, result?: any, msg?: string }>} + */ +module.exports = async function apiDeleteUser({ + dbFullName, + deletedUserId, + useLocal, +}) { + const existingUserQuery = `SELECT * FROM users WHERE id = ?`; + const existingUserValues = [deletedUserId]; + + const existingUser = await varDatabaseDbHandler({ + queryString: existingUserQuery, + queryValuesArray: existingUserValues, + database: dbFullName, + useLocal, + }); + + if (!existingUser?.[0]) { + return { + success: false, + msg: "User not found", + }; + } + + const deleteUser = await deleteDbEntry({ + dbContext: "Dsql User", + paradigm: "Full Access", + dbFullName, + tableName: "users", + identifierColumnName: "id", + identifierValue: deletedUserId, + useLocal, + }); + + return { + success: true, + result: deleteUser, + }; +}; diff --git a/package-shared/functions/api/users/api-get-user.js b/package-shared/functions/api/users/api-get-user.js index 88a6b32..b086e26 100644 --- a/package-shared/functions/api/users/api-get-user.js +++ b/package-shared/functions/api/users/api-get-user.js @@ -1,6 +1,5 @@ // @ts-check -const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); /** @type {import("../../../types").APIGetUserFunction} */ @@ -12,13 +11,12 @@ module.exports = async function apiGetUser({ }) { const query = `SELECT ${fields.join(",")} FROM users WHERE id=?`; - let foundUser = useLocal - ? await LOCAL_DB_HANDLER(query, [userId]) - : await varDatabaseDbHandler({ - queryString: query, - queryValuesArray: [userId], - database: dbFullName.replace(/[^a-z0-9_]/g, ""), - }); + let foundUser = await varDatabaseDbHandler({ + queryString: query, + queryValuesArray: [userId], + database: dbFullName.replace(/[^a-z0-9_]/g, ""), + useLocal, + }); if (!foundUser || !foundUser[0]) { return { diff --git a/package-shared/functions/api/users/api-login.js b/package-shared/functions/api/users/api-login.js index f5e6c13..d721937 100644 --- a/package-shared/functions/api/users/api-login.js +++ b/package-shared/functions/api/users/api-login.js @@ -1,6 +1,7 @@ // @ts-check const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); +const { writeAuthFile } = require("../../backend/auth/write-auth-files"); const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); const hashPassword = require("../../dsql/hashPassword"); @@ -50,16 +51,12 @@ module.exports = async function apiLoginUser({ }) : null; - let foundUser = useLocal - ? await LOCAL_DB_HANDLER( - `SELECT * FROM users WHERE email = ? OR username = ?`, - [email, username] - ) - : await varDatabaseDbHandler({ - queryString: `SELECT * FROM users WHERE email = ? OR username = ?`, - queryValuesArray: [email, username], - database: dbFullName.replace(/[^a-z0-9_]/g, ""), - }); + let foundUser = await varDatabaseDbHandler({ + queryString: `SELECT * FROM users WHERE email = ? OR username = ?`, + queryValuesArray: [email, username], + database: dbFullName.replace(/[^a-z0-9_]/g, ""), + useLocal, + }); if ((!foundUser || !foundUser[0]) && !social) return { @@ -107,16 +104,12 @@ module.exports = async function apiLoginUser({ } if (isPasswordCorrect && email_login) { - const resetTempCode = useLocal - ? await LOCAL_DB_HANDLER( - `UPDATE users SET ${email_login_field} = ? WHERE email = ? OR username = ?`, - ["", email, username] - ) - : await varDatabaseDbHandler({ - queryString: `UPDATE users SET ${email_login_field} = ? WHERE email = ? OR username = ?`, - queryValuesArray: ["", email, username], - database: dbFullName.replace(/[^a-z0-9_]/g, ""), - }); + const resetTempCode = await varDatabaseDbHandler({ + queryString: `UPDATE users SET ${email_login_field} = ? WHERE email = ? OR username = ?`, + queryValuesArray: ["", email, username], + database: dbFullName.replace(/[^a-z0-9_]/g, ""), + useLocal, + }); } let csrfKey = @@ -144,6 +137,7 @@ module.exports = async function apiLoginUser({ date: Date.now(), }; + /** @type {import("../../../types").APILoginFunctionReturn} */ const resposeObject = { success: true, msg: "Login Successful", @@ -152,6 +146,7 @@ module.exports = async function apiLoginUser({ userPayload ), userId: foundUser[0].id, + csrf: csrfKey, }; if ( diff --git a/package-shared/functions/api/users/api-reauth-user.d.ts b/package-shared/functions/api/users/api-reauth-user.d.ts index 9dad88c..e7e8b74 100644 --- a/package-shared/functions/api/users/api-reauth-user.d.ts +++ b/package-shared/functions/api/users/api-reauth-user.d.ts @@ -1,10 +1,9 @@ -declare function _exports({ existingUser, database, userId, additionalFields, useLocal, }: { +declare function _exports({ existingUser, database, additionalFields, useLocal, }: { existingUser: { [x: string]: any; }; database: string; - userId?: string | number; additionalFields?: string[]; useLocal?: boolean; -}): Promise; +}): Promise; export = _exports; diff --git a/package-shared/functions/api/users/api-reauth-user.js b/package-shared/functions/api/users/api-reauth-user.js index 52e5801..a47d5cb 100644 --- a/package-shared/functions/api/users/api-reauth-user.js +++ b/package-shared/functions/api/users/api-reauth-user.js @@ -9,16 +9,14 @@ const nodemailer = require("nodemailer"); * @param {object} param * @param {Object} param.existingUser * @param {string} param.database - * @param {string | number} [param.userId] * @param {string[]} [param.additionalFields] * @param {boolean} [param.useLocal] * - * @returns {Promise} + * @returns {Promise} */ module.exports = async function apiReauthUser({ existingUser, database, - userId, additionalFields, useLocal, }) { @@ -55,7 +53,7 @@ module.exports = async function apiReauthUser({ "-" + Math.random().toString(36).substring(2); - /** @type {Object} */ + /** @type {import("../../../types").DATASQUIREL_LoggedInUser} */ let userPayload = { id: foundUser[0].id, first_name: foundUser[0].first_name, @@ -94,6 +92,6 @@ module.exports = async function apiReauthUser({ success: true, msg: "Login Successful", payload: userPayload, - userId, + csrf: csrfKey, }; }; diff --git a/package-shared/functions/api/users/api-send-email-code.js b/package-shared/functions/api/users/api-send-email-code.js index 445639d..5dc6c33 100644 --- a/package-shared/functions/api/users/api-send-email-code.js +++ b/package-shared/functions/api/users/api-send-email-code.js @@ -47,13 +47,12 @@ module.exports = async function apiSendEmailCode({ const foundUserQuery = `SELECT * FROM users WHERE email = ?`; const foundUserValues = [email]; - let foundUser = useLocal - ? await LOCAL_DB_HANDLER(foundUserQuery, foundUserValues) - : await varDatabaseDbHandler({ - queryString: foundUserQuery, - queryValuesArray: foundUserValues, - database, - }); + let foundUser = await varDatabaseDbHandler({ + queryString: foundUserQuery, + queryValuesArray: foundUserValues, + database, + useLocal, + }); //////////////////////////////////////// //////////////////////////////////////// @@ -105,13 +104,12 @@ module.exports = async function apiSendEmailCode({ const setTempCodeQuery = `UPDATE users SET ${email_login_field} = ? WHERE email = ?`; const setTempCodeValues = [tempCode + `-${Date.now()}`, email]; - let setTempCode = useLocal - ? await LOCAL_DB_HANDLER(setTempCodeQuery, setTempCodeValues) - : await varDatabaseDbHandler({ - queryString: setTempCodeQuery, - queryValuesArray: setTempCodeValues, - database: database, - }); + let setTempCode = await varDatabaseDbHandler({ + queryString: setTempCodeQuery, + queryValuesArray: setTempCodeValues, + database: database, + useLocal, + }); } return { diff --git a/package-shared/functions/api/users/api-update-user.d.ts b/package-shared/functions/api/users/api-update-user.d.ts index 883de4f..bf0df17 100644 --- a/package-shared/functions/api/users/api-update-user.d.ts +++ b/package-shared/functions/api/users/api-update-user.d.ts @@ -1,13 +1,14 @@ -declare function _exports({ payload, dbFullName, useLocal, }: { +declare function _exports({ payload, dbFullName, updatedUserId, useLocal, dbSchema, }: { payload: { - id: string | number; - } & { - [x: string]: (string | number | null | undefined); + [x: string]: any; }; dbFullName: string; + updatedUserId: string | number; useLocal?: boolean; + dbSchema?: import("../../../types").DSQL_DatabaseSchemaType; }): Promise<{ success: boolean; - payload: any; + payload?: any; + msg?: string; }>; export = _exports; diff --git a/package-shared/functions/api/users/api-update-user.js b/package-shared/functions/api/users/api-update-user.js index 3086984..a03c158 100644 --- a/package-shared/functions/api/users/api-update-user.js +++ b/package-shared/functions/api/users/api-update-user.js @@ -1,33 +1,83 @@ // @ts-check +const LOCAL_DB_HANDLER = require("../../../utils/backend/global-db/LOCAL_DB_HANDLER"); const updateDbEntry = require("../../backend/db/updateDbEntry"); +const encrypt = require("../../dsql/encrypt"); +const hashPassword = require("../../dsql/hashPassword"); +const varDatabaseDbHandler = require("../../backend/varDatabaseDbHandler"); /** * # Update API User Function * * @param {object} params - * @param {{ id: string | number } & Object} params.payload + * @param {Object} params.payload * @param {string} params.dbFullName + * @param {string | number} params.updatedUserId * @param {boolean} [params.useLocal] + * @param {import("../../../types").DSQL_DatabaseSchemaType} [params.dbSchema] * - * @returns {Promise<{ success: boolean, payload: any }>} + * @returns {Promise<{ success: boolean, payload?: any, msg?: string }>} */ module.exports = async function apiUpdateUser({ payload, dbFullName, + updatedUserId, useLocal, + dbSchema, }) { + const existingUserQuery = `SELECT * FROM users WHERE id = ?`; + const existingUserValues = [updatedUserId]; + + const existingUser = await varDatabaseDbHandler({ + queryString: existingUserQuery, + queryValuesArray: existingUserValues, + database: dbFullName, + useLocal, + }); + + if (!existingUser?.[0]) { + return { + success: false, + msg: "User not found", + }; + } + const data = (() => { const reqBodyKeys = Object.keys(payload); + const targetTableSchema = (() => { + try { + const targetDatabaseSchema = dbSchema?.tables?.find( + (tbl) => tbl.tableName == "users" + ); + return targetDatabaseSchema; + } catch (error) { + return undefined; + } + })(); + /** @type {any} */ const finalData = {}; reqBodyKeys.forEach((key) => { - if (key?.match(/^date_|^id$/)) return; - finalData[key] = payload[key]; + const targetFieldSchema = targetTableSchema?.fields?.find( + (field) => field.fieldName == key + ); + + if (key?.match(/^date_|^id$|^uuid$/)) return; + let value = payload[key]; + + if (targetFieldSchema?.encrypted) { + value = encrypt({ data: value }); + } + + finalData[key] = value; }); + if (finalData.password && typeof finalData.password == "string") { + finalData.password = hashPassword({ password: finalData.password }); + } + return finalData; })(); @@ -37,7 +87,7 @@ module.exports = async function apiUpdateUser({ dbFullName, tableName: "users", identifierColumnName: "id", - identifierValue: payload.id, + identifierValue: updatedUserId, data: data, useLocal, }); diff --git a/package-shared/functions/api/users/social/api-github-login.d.ts b/package-shared/functions/api/users/social/api-github-login.d.ts index 975fccb..b63c820 100644 --- a/package-shared/functions/api/users/social/api-github-login.d.ts +++ b/package-shared/functions/api/users/social/api-github-login.d.ts @@ -3,9 +3,7 @@ declare function _exports({ code, clientId, clientSecret, database, additionalFi clientId?: string; clientSecret?: string; database?: string; - additionalFields?: { - [x: string]: any; - }; + additionalFields?: string[]; res?: any; email?: string; userId?: string | number; diff --git a/package-shared/functions/api/users/social/api-github-login.js b/package-shared/functions/api/users/social/api-github-login.js index 1efb5a7..e7843e1 100644 --- a/package-shared/functions/api/users/social/api-github-login.js +++ b/package-shared/functions/api/users/social/api-github-login.js @@ -11,7 +11,7 @@ const camelJoinedtoCamelSpace = require("../../../../utils/camelJoinedtoCamelSpa * @param {string} [param.clientId] * @param {string} [param.clientSecret] * @param {string} [param.database] - * @param {Object} [param.additionalFields] + * @param {string[]} [param.additionalFields] * @param {any} [param.res] * @param {string} [param.email] * @param {string | number} [param.userId] @@ -84,19 +84,11 @@ module.exports = async function apiGithubLogin({ username: "github-user-" + socialId, }; - if (additionalFields && Object.keys(additionalFields).length > 0) { - Object.keys(additionalFields).forEach((key) => { - // @ts-ignore - payload[key] = additionalFields[key]; - }); - } - const loggedInGithubUser = await handleSocialDb({ database, email: gitHubUser.email, payload: payload, social_platform: "github", - res: res, social_id: socialId, supEmail: email, additionalFields, @@ -106,5 +98,5 @@ module.exports = async function apiGithubLogin({ //////////////////////////////////////////////// //////////////////////////////////////////////// - return { success: true, ...loggedInGithubUser, dsqlUserId: userId }; + return { ...loggedInGithubUser }; }; diff --git a/package-shared/functions/api/users/social/api-google-login.js b/package-shared/functions/api/users/social/api-google-login.js index 61ec7dd..725ee6f 100644 --- a/package-shared/functions/api/users/social/api-google-login.js +++ b/package-shared/functions/api/users/social/api-google-login.js @@ -1,88 +1,96 @@ // @ts-check -const { OAuth2Client } = require("google-auth-library"); +const https = require("https"); const handleSocialDb = require("../../social-login/handleSocialDb"); +const EJSON = require("../../../../utils/ejson"); /** @type {import("../../../../types").APIGoogleLoginFunction} */ module.exports = async function apiGoogleLogin({ - clientId, token, database, - userId, additionalFields, - res, }) { - const client = new OAuth2Client(clientId); + try { + /** @type {import("../../../../types").GoogleOauth2User | undefined} */ + const gUser = await new Promise((resolve, reject) => { + https + .request( + { + method: "GET", + hostname: "www.googleapis.com", + path: "/oauth2/v3/userinfo", + headers: { + Authorization: `Bearer ${token}`, + }, + }, + (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + resolve(/** @type {any} */ (EJSON.parse(data))); + }); + } + ) + .end(); + }); - const ticket = await client.verifyIdToken({ - idToken: token, - audience: clientId, - }); + if (!gUser?.email_verified) throw new Error("No Google User."); - if (!ticket?.getPayload()?.email_verified) { - return { - success: false, - user: null, + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// + + 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 { given_name, family_name, email, sub, picture } = gUser; + + /** @type {Object} */ + 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}`, }; - } - const payload = ticket.getPayload(); + const loggedInGoogleUser = await handleSocialDb({ + database, + email: email || "", + payload: payloadObject, + social_platform: "google", + social_id: sub, + additionalFields, + }); - if (!payload) throw new Error("No Payload"); + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// + return { ...loggedInGoogleUser }; + } catch (/** @type {any} */ error) { + console.log(`apo-google-login.js ERROR: ${error.message}`); - 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)", + msg: error.message, }; } - - /** - * Create new user folder and file - * - * @description Create new user folder and file - */ - const targetDbName = `datasquirel_user_${userId}_${database}`; - - const { given_name, family_name, email, sub, picture, email_verified } = - payload; - - /** @type {Object} */ - 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({ - res, - database: targetDbName, - email: email || "", - payload: payloadObject, - social_platform: "google", - social_id: sub, - additionalFields, - }); - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - - return { success: true, ...loggedInGoogleUser, dsqlUserId: userId }; }; diff --git a/package-shared/functions/backend/addAdminUserOnLogin.d.ts b/package-shared/functions/backend/addAdminUserOnLogin.d.ts index 9f84e83..8c88dcb 100644 --- a/package-shared/functions/backend/addAdminUserOnLogin.d.ts +++ b/package-shared/functions/backend/addAdminUserOnLogin.d.ts @@ -1,10 +1,11 @@ -declare function _exports({ query, user }: { +declare function _exports({ query, user, useLocal }: { query: { invite: number; database_access: string; priviledge: string; email: string; }; - user: import("../../types").UserType; + useLocal?: boolean; + user: import("../../types").DATASQUIREL_LoggedInUser; }): Promise; export = _exports; diff --git a/package-shared/functions/backend/addAdminUserOnLogin.js b/package-shared/functions/backend/addAdminUserOnLogin.js index fce9069..d034302 100755 --- a/package-shared/functions/backend/addAdminUserOnLogin.js +++ b/package-shared/functions/backend/addAdminUserOnLogin.js @@ -3,13 +3,7 @@ const serverError = require("./serverError"); const DB_HANDLER = require("../../utils/backend/global-db/DB_HANDLER"); const addDbEntry = require("./db/addDbEntry"); - -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ +const LOCAL_DB_HANDLER = require("../../utils/backend/global-db/LOCAL_DB_HANDLER"); /** * Add Admin User on Login @@ -27,32 +21,23 @@ const addDbEntry = require("./db/addDbEntry"); * @param {string} params.query.priviledge - String containing databases priviledges * @param {string} params.query.email - Inviting user email address * - * @param {import("../../types").UserType} params.user - invited user object + * @param {boolean} [params.useLocal] + * @param {import("../../types").DATASQUIREL_LoggedInUser} params.user - invited user object * * @returns {Promise} new user auth object payload */ -module.exports = async function addAdminUserOnLogin({ query, user }) { +module.exports = async function addAdminUserOnLogin({ query, user, useLocal }) { try { - /** - * Fetch user - * - * @description Fetch user from db - */ // @ts-ignore + const finalDbHandler = useLocal ? LOCAL_DB_HANDLER : DB_HANDLER; const { invite, database_access, priviledge, email } = query; - const lastInviteTimeArray = await DB_HANDLER( - `SELECT date_created_code FROM invitations WHERE inviting_user_id=? AND invited_user_email=?`, - [invite, email] - ); + const lastInviteTimeQuery = `SELECT date_created_code FROM invitations WHERE inviting_user_id=? AND invited_user_email=?`; + const lastInviteTimeValues = [invite, email]; - // if (lastInviteTimeArray && lastInviteTimeArray[0]?.date_created_code) { - // const timeSinceLastInvite = Date.now() - parseInt(lastInviteTimeArray[0].date_created_code); - // if (timeSinceLastInvite > 21600000) { - // throw new Error("Invitation expired"); - // } - // } else if (!lastInviteTimeArray || !lastInviteTimeArray[0]) { - // throw new Error("No Invitation Found"); - // } + const lastInviteTimeArray = await finalDbHandler( + lastInviteTimeQuery, + lastInviteTimeValues + ); if (!lastInviteTimeArray || !lastInviteTimeArray[0]) { throw new Error("No Invitation Found"); @@ -62,14 +47,16 @@ module.exports = async function addAdminUserOnLogin({ query, user }) { //////////////////////////////////////////////// //////////////////////////////////////////////// - // @ts-ignore - const invitingUserDb = await DB_HANDLER( - `SELECT first_name,last_name,email FROM users WHERE id=?`, - [invite] + const invitingUserDbQuery = `SELECT first_name,last_name,email FROM users WHERE id=?`; + const invitingUserDbValues = [invite]; + + const invitingUserDb = await finalDbHandler( + invitingUserDbQuery, + invitingUserDbValues ); if (invitingUserDb?.[0]) { - const existingUserUser = await DB_HANDLER( + const existingUserUser = await finalDbHandler( `SELECT email FROM user_users WHERE user_id=? AND invited_user_id=? AND user_type='admin' AND email=?`, [invite, user.id, email] ); @@ -77,25 +64,6 @@ module.exports = async function addAdminUserOnLogin({ query, user }) { if (existingUserUser?.[0]) { console.log("User already added"); } else { - // const newUserUser = await DB_HANDLER( - // `INSERT IGNORE INTO user_users - // (user_id, invited_user_id, database_access, first_name, last_name, phone, email, username, user_type, user_priviledge) - // VALUES - // (?,?,?,?,?,?,?,?,?,?) - // )`, - // [ - // invite, - // user.id, - // database_access, - // user.first_name, - // user.last_name, - // user.phone, - // user.email, - // user.username, - // "admin", - // priviledge, - // ] - // ); addDbEntry({ dbFullName: "datasquirel", tableName: "user_users", @@ -113,20 +81,19 @@ module.exports = async function addAdminUserOnLogin({ query, user }) { image: user.image, image_thumbnail: user.image_thumbnail, }, + useLocal, }); //////////////////////////////////////////////// //////////////////////////////////////////////// //////////////////////////////////////////////// - // @ts-ignore - const dbTableData = await DB_HANDLER( + const dbTableData = await finalDbHandler( `SELECT db_tables_data FROM invitations WHERE inviting_user_id=? AND invited_user_email=?`, [invite, email] ); - // @ts-ignore - const clearEntries = await DB_HANDLER( + const clearEntries = await finalDbHandler( `DELETE FROM delegated_user_tables WHERE root_user_id=? AND delegated_user_id=?`, [invite, user.id] ); @@ -154,17 +121,13 @@ module.exports = async function addAdminUserOnLogin({ query, user }) { table: table_slug, priviledge: priviledge, }, + useLocal, }); } } - - //////////////////////////////////////////////// - //////////////////////////////////////////////// - //////////////////////////////////////////////// } - // @ts-ignore - const inviteAccepted = await DB_HANDLER( + const inviteAccepted = await finalDbHandler( `UPDATE invitations SET invitation_status='Accepted' WHERE inviting_user_id=? AND invited_user_email=?`, [invite, email] ); diff --git a/package-shared/functions/backend/addUsersTableToDb.d.ts b/package-shared/functions/backend/addUsersTableToDb.d.ts index d2073bf..798532c 100644 --- a/package-shared/functions/backend/addUsersTableToDb.d.ts +++ b/package-shared/functions/backend/addUsersTableToDb.d.ts @@ -1,6 +1,9 @@ -declare function _exports({ userId, database, useLocal, }: { +declare function _exports({ userId, database, useLocal, payload, }: { userId: number; database: string; useLocal?: boolean; + payload?: { + [x: string]: any; + }; }): Promise; export = _exports; diff --git a/package-shared/functions/backend/addUsersTableToDb.js b/package-shared/functions/backend/addUsersTableToDb.js index 5c17278..9b3e699 100755 --- a/package-shared/functions/backend/addUsersTableToDb.js +++ b/package-shared/functions/backend/addUsersTableToDb.js @@ -10,6 +10,7 @@ const { default: setUserSchemaData } = require("./setUserSchemaData"); const addDbEntry = require("./db/addDbEntry"); const createDbFromSchema = require("../../shell/createDbFromSchema"); const LOCAL_DB_HANDLER = require("../../utils/backend/global-db/LOCAL_DB_HANDLER"); +const grabNewUsersTableSchema = require("./grabNewUsersTableSchema"); /** * # Add User Table to Database @@ -18,6 +19,7 @@ const LOCAL_DB_HANDLER = require("../../utils/backend/global-db/LOCAL_DB_HANDLER * @param {number} params.userId - user id * @param {string} params.database * @param {boolean} [params.useLocal] + * @param {Object} [params.payload] - payload object * * @returns {Promise} new user auth object payload */ @@ -25,39 +27,30 @@ module.exports = async function addUsersTableToDb({ userId, database, useLocal, + payload, }) { - /** - * Initialize - * - * @description Initialize - */ - const dbFullName = `datasquirel_user_${userId}_${database}`; - /** @type {import("../../types").DSQL_TableSchemaType} */ - const userPreset = require("../../data/presets/users.json"); - try { - /** - * Fetch user - * - * @description Fetch user from db - */ + const dbFullName = database; + + const userPreset = grabNewUsersTableSchema({ payload }); + if (!userPreset) throw new Error("Couldn't Get User Preset!"); + const userSchemaData = grabUserSchemaData({ userId }); if (!userSchemaData) throw new Error("User schema data not found!"); - let targetDatabase = userSchemaData.filter( - (db) => db.dbSlug === database - )[0]; + let targetDatabase = userSchemaData.find( + (db) => db.dbFullName === database + ); - let existingTableIndex; - // @ts-ignore - let existingTable = targetDatabase.tables.filter((table, index) => { - if (table.tableName === "users") { - existingTableIndex = index; - return true; - } - }); + if (!targetDatabase) { + throw new Error("Couldn't Find Target Database!"); + } - if (existingTable && existingTable[0] && existingTableIndex) { + let existingTableIndex = targetDatabase?.tables.findIndex( + (table) => table.tableName === "users" + ); + + if (typeof existingTableIndex == "number" && existingTableIndex > 0) { targetDatabase.tables[existingTableIndex] = userPreset; } else { targetDatabase.tables.push(userPreset); @@ -65,6 +58,7 @@ module.exports = async function addUsersTableToDb({ setUserSchemaData({ schemaData: userSchemaData, userId }); + /** @type {any[] | null} */ const targetDb = useLocal ? await LOCAL_DB_HANDLER( `SELECT id FROM user_databases WHERE user_id=? AND db_slug=?`, @@ -75,14 +69,14 @@ module.exports = async function addUsersTableToDb({ [userId, database] ); - if (targetDb && targetDb[0]) { + if (targetDb?.[0]) { const newTableEntry = await addDbEntry({ dbFullName: "datasquirel", tableName: "user_database_tables", data: { user_id: userId, db_id: targetDb[0].id, - db_slug: database, + db_slug: targetDatabase.dbSlug, table_name: "Users", table_slug: "users", }, @@ -97,6 +91,8 @@ module.exports = async function addUsersTableToDb({ return `Done!`; } catch (/** @type {any} */ error) { + console.log(`addUsersTableToDb.js ERROR: ${error.message}`); + serverError({ component: "addUsersTableToDb", message: error.message, diff --git a/package-shared/functions/backend/auth/write-auth-files.d.ts b/package-shared/functions/backend/auth/write-auth-files.d.ts new file mode 100644 index 0000000..a37c4b5 --- /dev/null +++ b/package-shared/functions/backend/auth/write-auth-files.d.ts @@ -0,0 +1,26 @@ +export function grabAuthDirs(): { + root: string; + auth: string; +}; +export function initAuthFiles(): boolean; +/** + * # Write Auth Files + * @param {string} name + * @param {string} data + */ +export function writeAuthFile(name: string, data: string): boolean; +/** + * # Get Auth Files + * @param {string} name + */ +export function getAuthFile(name: string): string; +/** + * # Delete Auth Files + * @param {string} name + */ +export function deleteAuthFile(name: string): void; +/** + * # Delete Auth Files + * @param {string} name + */ +export function checkAuthFile(name: string): boolean; diff --git a/package-shared/functions/backend/auth/write-auth-files.js b/package-shared/functions/backend/auth/write-auth-files.js new file mode 100644 index 0000000..6855bfc --- /dev/null +++ b/package-shared/functions/backend/auth/write-auth-files.js @@ -0,0 +1,90 @@ +// @ts-check + +const fs = require("fs"); +const path = require("path"); + +const grabAuthDirs = () => { + const ROOT_DIR = path.resolve(process.cwd(), "./.tmp"); + const AUTH_DIR = path.join(ROOT_DIR, "logins"); + + return { root: ROOT_DIR, auth: AUTH_DIR }; +}; + +const initAuthFiles = () => { + try { + const authDirs = grabAuthDirs(); + + if (!fs.existsSync(authDirs.root)) + fs.mkdirSync(authDirs.root, { recursive: true }); + if (!fs.existsSync(authDirs.auth)) + fs.mkdirSync(authDirs.auth, { recursive: true }); + return true; + } catch (/** @type {any} */ error) { + console.log(`Error initializing Auth Files: ${error.message}`); + return false; + } +}; + +/** + * # Write Auth Files + * @param {string} name + * @param {string} data + */ +const writeAuthFile = (name, data) => { + initAuthFiles(); + try { + fs.writeFileSync(path.join(grabAuthDirs().auth, name), data); + return true; + } catch (/** @type {any} */ error) { + console.log(`Error writing Auth File: ${error.message}`); + return false; + } +}; + +/** + * # Get Auth Files + * @param {string} name + */ +const getAuthFile = (name) => { + try { + const authFilePath = path.join(grabAuthDirs().auth, name); + return fs.readFileSync(authFilePath, "utf-8"); + } catch (/** @type {any} */ error) { + console.log(`Error getting Auth File: ${error.message}`); + return null; + } +}; + +/** + * # Delete Auth Files + * @param {string} name + */ +const deleteAuthFile = (name) => { + try { + return fs.rmSync(path.join(grabAuthDirs().auth, name)); + } catch (/** @type {any} */ error) { + console.log(`Error deleting Auth File: ${error.message}`); + return null; + } +}; + +/** + * # Delete Auth Files + * @param {string} name + */ +const checkAuthFile = (name) => { + try { + return fs.existsSync(path.join(grabAuthDirs().auth, name)); + return true; + } catch (/** @type {any} */ error) { + console.log(`Error checking Auth File: ${error.message}`); + return false; + } +}; + +exports.grabAuthDirs = grabAuthDirs; +exports.initAuthFiles = initAuthFiles; +exports.writeAuthFile = writeAuthFile; +exports.getAuthFile = getAuthFile; +exports.deleteAuthFile = deleteAuthFile; +exports.checkAuthFile = checkAuthFile; diff --git a/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts b/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts index d3f9c17..ea661a3 100644 --- a/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts +++ b/package-shared/functions/backend/cookies/get-auth-cookie-names.d.ts @@ -1,4 +1,7 @@ -declare function _exports(): { +declare function _exports(params?: { + database?: string; + userId?: string | number; +}): { keyCookieName: string; csrfCookieName: string; }; diff --git a/package-shared/functions/backend/cookies/get-auth-cookie-names.js b/package-shared/functions/backend/cookies/get-auth-cookie-names.js index 374dd26..5168adc 100644 --- a/package-shared/functions/backend/cookies/get-auth-cookie-names.js +++ b/package-shared/functions/backend/cookies/get-auth-cookie-names.js @@ -1,10 +1,28 @@ -module.exports = function getAuthCookieNames() { +// @ts-check + +/** + * # Grab Auth Cookie Names + * + * @param {object} [params] + * @param {string} [params.database] + * @param {string | number} [params.userId] + * + * @returns {{ keyCookieName: string, csrfCookieName: 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 keyCookieName = cookiesPrefix + cookiesKeyName; - const csrfCookieName = cookiesPrefix + cookiesCSRFName; + let keyCookieName = cookiesPrefix; + if (params?.userId) keyCookieName += `user_${params.userId}_`; + if (params?.database) keyCookieName += `${params.database}_`; + keyCookieName += cookiesKeyName; + + let csrfCookieName = cookiesPrefix; + if (params?.userId) csrfCookieName += `user_${params.userId}_`; + if (params?.database) csrfCookieName += `${params.database}_`; + csrfCookieName += cookiesCSRFName; return { keyCookieName, diff --git a/package-shared/functions/backend/grabNewUsersTableSchema.d.ts b/package-shared/functions/backend/grabNewUsersTableSchema.d.ts new file mode 100644 index 0000000..350ce2d --- /dev/null +++ b/package-shared/functions/backend/grabNewUsersTableSchema.d.ts @@ -0,0 +1,6 @@ +declare function _exports(params?: { + payload?: { + [x: string]: any; + }; +}): import("../../types").DSQL_TableSchemaType | null; +export = _exports; diff --git a/package-shared/functions/backend/grabNewUsersTableSchema.js b/package-shared/functions/backend/grabNewUsersTableSchema.js new file mode 100755 index 0000000..d31fd6d --- /dev/null +++ b/package-shared/functions/backend/grabNewUsersTableSchema.js @@ -0,0 +1,54 @@ +// @ts-check + +const grabSchemaFieldsFromData = require("./grabSchemaFieldsFromData"); +const serverError = require("./serverError"); + +/** + * # Add User Table to Database + * + * @param {object} [params] + * @param {Object} [params.payload] - fields to add to the table + * + * @returns {import("../../types").DSQL_TableSchemaType | null} new user auth object payload + */ +module.exports = function grabNewUsersTableSchema(params) { + try { + /** @type {import("../../types").DSQL_TableSchemaType} */ + const userPreset = require("../../data/presets/users.json"); + /** @type {import("../../types").DSQL_FieldSchemaType[]} */ + const defaultFields = require("../../data/defaultFields.json"); + + const supplementalFields = params?.payload + ? grabSchemaFieldsFromData({ + data: params?.payload, + excludeData: defaultFields, + excludeFields: userPreset.fields, + }) + : []; + + console.log("supplementalFields", supplementalFields); + + const allFields = [...userPreset.fields, ...supplementalFields]; + + console.log("allFields", allFields); + + const finalFields = [ + ...defaultFields.slice(0, 2), + ...allFields, + ...defaultFields.slice(2), + ]; + + userPreset.fields = [...finalFields]; + + return userPreset; + } catch (/** @type {any} */ error) { + console.log(`grabNewUsersTableSchema.js ERROR: ${error.message}`); + + serverError({ + component: "grabNewUsersTableSchema", + message: error.message, + }); + + return null; + } +}; diff --git a/package-shared/functions/backend/grabSchemaFieldsFromData.d.ts b/package-shared/functions/backend/grabSchemaFieldsFromData.d.ts new file mode 100644 index 0000000..1ff25bf --- /dev/null +++ b/package-shared/functions/backend/grabSchemaFieldsFromData.d.ts @@ -0,0 +1,11 @@ +declare function _exports({ data, fields, excludeData, excludeFields, }: { + data?: { + [x: string]: any; + }; + fields?: string[]; + excludeData?: { + [x: string]: any; + }; + excludeFields?: import("../../types").DSQL_FieldSchemaType[]; +}): import("../../types").DSQL_FieldSchemaType[]; +export = _exports; diff --git a/package-shared/functions/backend/grabSchemaFieldsFromData.js b/package-shared/functions/backend/grabSchemaFieldsFromData.js new file mode 100755 index 0000000..ec813db --- /dev/null +++ b/package-shared/functions/backend/grabSchemaFieldsFromData.js @@ -0,0 +1,94 @@ +// @ts-check + +const serverError = require("./serverError"); + +/** + * # Add User Table to Database + * + * @param {object} params + * @param {Object} [params.data] + * @param {string[]} [params.fields] + * @param {Object} [params.excludeData] + * @param {import("../../types").DSQL_FieldSchemaType[]} [params.excludeFields] + * + * @returns {import("../../types").DSQL_FieldSchemaType[]} new user auth object payload + */ +module.exports = function grabSchemaFieldsFromData({ + data, + fields, + excludeData, + excludeFields, +}) { + try { + const possibleFields = require("../../data/possibleFields.json"); + const dataTypes = require("../../data/dataTypes.json"); + + /** @type {import("../../types").DSQL_FieldSchemaType[]} */ + const finalFields = []; + + /** @type {string[]} */ + let filteredFields = []; + + if (data && Object.keys(data)?.[0]) { + filteredFields = Object.keys(data); + } + + if (fields) { + filteredFields = [...filteredFields, ...fields]; + filteredFields = [...new Set(filteredFields)]; + } + + filteredFields = filteredFields + .filter( + (fld) => !excludeData || !Object.keys(excludeData).includes(fld) + ) + .filter( + (fld) => + !excludeFields || + !excludeFields.find((exlFld) => exlFld.fieldName == fld) + ); + + filteredFields.forEach((fld) => { + const value = data ? data[fld] : null; + + if (typeof value == "string") { + const newField = + /** @type {import("../../types").DSQL_FieldSchemaType} */ ({ + fieldName: fld, + dataType: value.length > 255 ? "TEXT" : "VARCHAR(255)", + }); + + if (Boolean(value.match(/<[^>]+>/g))) { + newField.richText = true; + } + + finalFields.push(newField); + } else if (typeof value == "number") { + finalFields.push( + /** @type {import("../../types").DSQL_FieldSchemaType} */ ({ + fieldName: fld, + dataType: "INT", + }) + ); + } else { + finalFields.push( + /** @type {import("../../types").DSQL_FieldSchemaType} */ ({ + fieldName: fld, + dataType: "VARCHAR(255)", + }) + ); + } + }); + + return finalFields; + } catch (/** @type {any} */ error) { + console.log(`grabSchemaFieldsFromData.js ERROR: ${error.message}`); + + serverError({ + component: "grabSchemaFieldsFromData.js", + message: error.message, + }); + + return []; + } +}; diff --git a/package-shared/functions/backend/passwordHash.js b/package-shared/functions/backend/passwordHash.js deleted file mode 100644 index 75b9611..0000000 --- a/package-shared/functions/backend/passwordHash.js +++ /dev/null @@ -1,31 +0,0 @@ -// @ts-check - -const { createHmac } = require("crypto"); -// - -/** - * # Password Hash function - * @param {string} password - * @returns - */ -function hashPassword(password) { - const hmac = createHmac( - "sha512", - process.env.DSQL_ENCRYPTION_PASSWORD || "" - ); - hmac.update(password); - let hashed = hmac.digest("base64"); - return hashed; -} - -exports.hashPassword = hashPassword; - -// export const comparePasswords = async (password) => { -// const hmac = createHmac("sha512", process.env.DSQL_ENCRYPTION_PASSWORD); -// hmac.update(password); -// let hashed = hmac.digest("base64"); - -// let dbPass = await global.DB_HANDLER(`SELECT * FROM users WHERE password = '${hashed}'`); -// console.log(dbPass); -// return dbPass; -// }; diff --git a/package-shared/functions/backend/serverError.js b/package-shared/functions/backend/serverError.js index f542691..7306aae 100755 --- a/package-shared/functions/backend/serverError.js +++ b/package-shared/functions/backend/serverError.js @@ -1,24 +1,11 @@ // @ts-check -/** - * ============================================================================== - * Imports - * ============================================================================== - */ const fs = require("fs"); const { IncomingMessage } = require("http"); -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ -/** ****************************************************************************** */ - /** - * ============================================================================== - * Main Function - * ============================================================================== + * # Server Error + * * @param {{ * user?: { id?: number | string, first_name?: string, last_name?: string, email?: string } & *, * message: string, diff --git a/package-shared/functions/backend/updateUsersTableSchema.d.ts b/package-shared/functions/backend/updateUsersTableSchema.d.ts new file mode 100644 index 0000000..c4d1b86 --- /dev/null +++ b/package-shared/functions/backend/updateUsersTableSchema.d.ts @@ -0,0 +1,9 @@ +declare function _exports({ userId, database, newFields, newPayload, }: { + userId: number | string; + database: string; + newFields?: string[]; + newPayload?: { + [x: string]: any; + }; +}): Promise; +export = _exports; diff --git a/package-shared/functions/backend/updateUsersTableSchema.js b/package-shared/functions/backend/updateUsersTableSchema.js new file mode 100755 index 0000000..ef8f13d --- /dev/null +++ b/package-shared/functions/backend/updateUsersTableSchema.js @@ -0,0 +1,80 @@ +// @ts-check + +const serverError = require("./serverError"); +const { default: grabUserSchemaData } = require("./grabUserSchemaData"); +const { default: setUserSchemaData } = require("./setUserSchemaData"); +const createDbFromSchema = require("../../shell/createDbFromSchema"); +const grabSchemaFieldsFromData = require("./grabSchemaFieldsFromData"); + +/** + * # Add User Table to Database + * + * @param {object} params + * @param {number | string} params.userId - user id + * @param {string} params.database + * @param {string[]} [params.newFields] - new fields to add to the users table + * @param {Object} [params.newPayload] + * + * @returns {Promise} new user auth object payload + */ +module.exports = async function updateUsersTableSchema({ + userId, + database, + newFields, + newPayload, +}) { + try { + const dbFullName = database; + + const userSchemaData = grabUserSchemaData({ userId }); + if (!userSchemaData) throw new Error("User schema data not found!"); + + let targetDatabaseIndex = userSchemaData.findIndex( + (db) => db.dbFullName === database + ); + + if (targetDatabaseIndex < 0) { + throw new Error("Couldn't Find Target Database!"); + } + + let existingTableIndex = userSchemaData[ + targetDatabaseIndex + ]?.tables.findIndex((table) => table.tableName === "users"); + + const usersTable = + userSchemaData[targetDatabaseIndex].tables[existingTableIndex]; + + if (!usersTable?.fields?.[0]) throw new Error("Users Table Not Found!"); + + const additionalFields = grabSchemaFieldsFromData({ + fields: newFields, + data: newPayload, + }); + + const spliceStartIndex = usersTable.fields.findIndex( + (field) => field.fieldName === "date_created" + ); + const finalSpliceStartIndex = + spliceStartIndex >= 0 ? spliceStartIndex : 0; + + usersTable.fields.splice(finalSpliceStartIndex, 0, ...additionalFields); + + setUserSchemaData({ schemaData: userSchemaData, userId }); + + const dbShellUpdate = await createDbFromSchema({ + userId, + targetDatabase: dbFullName, + }); + + return `Done!`; + } catch (/** @type {any} */ error) { + console.log(`addUsersTableToDb.js ERROR: ${error.message}`); + + serverError({ + component: "addUsersTableToDb", + message: error.message, + user: { id: userId }, + }); + return error.message; + } +}; diff --git a/package-shared/functions/backend/varDatabaseDbHandler.d.ts b/package-shared/functions/backend/varDatabaseDbHandler.d.ts index 5dfd99e..b653b3f 100644 --- a/package-shared/functions/backend/varDatabaseDbHandler.d.ts +++ b/package-shared/functions/backend/varDatabaseDbHandler.d.ts @@ -1,7 +1,8 @@ -declare function _exports({ queryString, queryValuesArray, database, tableSchema, }: { +declare function _exports({ queryString, queryValuesArray, database, tableSchema, useLocal, }: { queryString: string; queryValuesArray?: any[]; database?: string; tableSchema?: import("../../types").DSQL_TableSchemaType; + useLocal?: boolean; }): Promise; export = _exports; diff --git a/package-shared/functions/backend/varDatabaseDbHandler.js b/package-shared/functions/backend/varDatabaseDbHandler.js index 0492068..c58d67e 100644 --- a/package-shared/functions/backend/varDatabaseDbHandler.js +++ b/package-shared/functions/backend/varDatabaseDbHandler.js @@ -5,6 +5,7 @@ const parseDbResults = require("./parseDbResults"); const serverError = require("./serverError"); const DB_HANDLER = require("../../utils/backend/global-db/DB_HANDLER"); const DSQL_USER_DB_HANDLER = require("../../utils/backend/global-db/DSQL_USER_DB_HANDLER"); +const LOCAL_DB_HANDLER = require("../../utils/backend/global-db/LOCAL_DB_HANDLER"); /** * DB handler for specific database @@ -15,6 +16,7 @@ const DSQL_USER_DB_HANDLER = require("../../utils/backend/global-db/DSQL_USER_DB * @param {*[]} [params.queryValuesArray] - Values Array * @param {string} [params.database] - Database name * @param {import("../../types").DSQL_TableSchemaType} [params.tableSchema] - Table schema + * @param {boolean} [params.useLocal] * @returns {Promise} */ module.exports = async function varDatabaseDbHandler({ @@ -22,6 +24,7 @@ module.exports = async function varDatabaseDbHandler({ queryValuesArray, database, tableSchema, + useLocal, }) { /** * Declare variables @@ -31,7 +34,11 @@ module.exports = async function varDatabaseDbHandler({ const isMaster = database?.match(/^datasquirel$/) ? true : false; /** @type {any} */ - const FINAL_DB_HANDLER = isMaster ? DB_HANDLER : DSQL_USER_DB_HANDLER; + const FINAL_DB_HANDLER = useLocal + ? LOCAL_DB_HANDLER + : isMaster + ? DB_HANDLER + : DSQL_USER_DB_HANDLER; let results; diff --git a/package-shared/functions/dsql/hashPassword.d.ts b/package-shared/functions/dsql/hashPassword.d.ts index f4be2fd..8f94a6b 100644 --- a/package-shared/functions/dsql/hashPassword.d.ts +++ b/package-shared/functions/dsql/hashPassword.d.ts @@ -1,5 +1,5 @@ declare function _exports({ password, encryptionKey }: { password: string; - encryptionKey: string; + encryptionKey?: string; }): string; export = _exports; diff --git a/package-shared/functions/dsql/hashPassword.js b/package-shared/functions/dsql/hashPassword.js index f511ea4..edbed31 100644 --- a/package-shared/functions/dsql/hashPassword.js +++ b/package-shared/functions/dsql/hashPassword.js @@ -1,13 +1,3 @@ -/** # MODULE TRACE -====================================================================== - * Detected 4 files that call this module. The files are listed below: -====================================================================== - * `require` Statement Found in [add-user.js] => file:///d:\GitHub\dsql\engine\user\add-user.js - * `require` Statement Found in [login-user.js] => file:///d:\GitHub\dsql\engine\user\login-user.js - * `require` Statement Found in [googleLogin.js] => file:///d:\GitHub\dsql\engine\user\social\utils\googleLogin.js - * `require` Statement Found in [update-user.js] => file:///d:\GitHub\dsql\engine\user\update-user.js -==== MODULE TRACE END ==== */ - // @ts-check const { createHmac } = require("crypto"); @@ -16,11 +6,18 @@ const { createHmac } = require("crypto"); * # Hash password Function * @param {object} param0 * @param {string} param0.password - Password to hash - * @param {string} param0.encryptionKey - Encryption key + * @param {string} [param0.encryptionKey] - Encryption key * @returns {string} */ module.exports = function hashPassword({ password, encryptionKey }) { - const hmac = createHmac("sha512", encryptionKey); + const finalEncryptionKey = + encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; + + if (!finalEncryptionKey?.match(/.{8,}/)) { + throw new Error("Encryption key is invalid"); + } + + const hmac = createHmac("sha512", finalEncryptionKey); hmac.update(password); let hashed = hmac.digest("base64"); return hashed; diff --git a/package-shared/shell/createDbFromSchema.d.ts b/package-shared/shell/createDbFromSchema.d.ts index 5bb17b5..5509679 100644 --- a/package-shared/shell/createDbFromSchema.d.ts +++ b/package-shared/shell/createDbFromSchema.d.ts @@ -1,7 +1,7 @@ export = createDbFromSchema; /** * Create database from Schema Function - * ============================================================================== + * ============================================= * @param {object} params - Single object params * @param {number|string|null} [params.userId] - User ID or null * @param {string} [params.targetDatabase] - User Database full name diff --git a/package-shared/shell/createDbFromSchema.js b/package-shared/shell/createDbFromSchema.js index 05089ba..1832ef4 100755 --- a/package-shared/shell/createDbFromSchema.js +++ b/package-shared/shell/createDbFromSchema.js @@ -16,7 +16,7 @@ const execFlag = process.argv.find((arg) => arg === "--exec"); /** * Create database from Schema Function - * ============================================================================== + * ============================================= * @param {object} params - Single object params * @param {number|string|null} [params.userId] - User ID or null * @param {string} [params.targetDatabase] - User Database full name diff --git a/package-shared/shell/utils/createTable.js b/package-shared/shell/utils/createTable.js index c43a14d..554f9ab 100755 --- a/package-shared/shell/utils/createTable.js +++ b/package-shared/shell/utils/createTable.js @@ -96,6 +96,8 @@ module.exports = async function createTable({ //////////////////////////////////////// let primaryKeySet = false; + + /** @type {import("../../types").DSQL_FieldSchemaType[]} */ let foreignKeys = []; //////////////////////////////////////// @@ -130,8 +132,7 @@ module.exports = async function createTable({ if (foreignKey) { foreignKeys.push({ - fieldName: fieldName, - ...foreignKey, + ...column, }); } @@ -161,14 +162,14 @@ module.exports = async function createTable({ if (foreignKeys[0]) { foreignKeys.forEach((foreighKey, index, array) => { - const { - fieldName, - destinationTableName, - destinationTableColumnName, - cascadeDelete, - cascadeUpdate, - foreignKeyName, - } = foreighKey; + const fieldName = foreighKey.fieldName; + const destinationTableName = + foreighKey.foreignKey?.destinationTableName; + const destinationTableColumnName = + foreighKey.foreignKey?.destinationTableColumnName; + const cascadeDelete = foreighKey.foreignKey?.cascadeDelete; + const cascadeUpdate = foreighKey.foreignKey?.cascadeUpdate; + const foreignKeyName = foreighKey.foreignKey?.foreignKeyName; const comma = (() => { if (index === foreignKeys.length - 1) return ""; diff --git a/package-shared/shell/utils/updateTable.js b/package-shared/shell/utils/updateTable.js index 12fd783..804602a 100755 --- a/package-shared/shell/utils/updateTable.js +++ b/package-shared/shell/utils/updateTable.js @@ -60,6 +60,9 @@ module.exports = async function updateTable({ * @description Initial setup */ + /** @type {any[]} */ + let errorLogs = []; + /** * @description Initialize table info array. This value will be * changing depending on if a field is renamed or not. @@ -530,7 +533,7 @@ module.exports = async function updateTable({ foreignKeyName, } = foreignKey; - const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (${fieldName}) REFERENCES ${destinationTableName}(${destinationTableColumnName})${ + const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`) REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)${ cascadeDelete ? " ON DELETE CASCADE" : "" }${cascadeUpdate ? " ON UPDATE CASCADE" : ""}`; // const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (${fieldName}) REFERENCES ${destinationTableName}(${destinationTableColumnName})${cascadeDelete ? " ON DELETE CASCADE" : ""}${cascadeUpdate ? " ON UPDATE CASCADE" : ""}` + ","; @@ -541,6 +544,10 @@ module.exports = async function updateTable({ database: dbFullName, queryString: finalQueryString, }); + + if (!addForeignKey?.serverStatus) { + errorLogs.push(addForeignKey); + } } //////////////////////////////////////// diff --git a/package-shared/types/index.d.ts b/package-shared/types/index.d.ts index 3375fe1..5ca394b 100644 --- a/package-shared/types/index.d.ts +++ b/package-shared/types/index.d.ts @@ -1,4 +1,3 @@ -import type { ServerResponse } from "http"; import { Editor } from "tinymce"; export type DSQL_DatabaseFullName = string; export interface DSQL_DatabaseSchemaType { @@ -165,7 +164,8 @@ export interface SerializeQueryParams { query: any; } export type DATASQUIREL_LoggedInUser = { - id?: number; + id: number; + uuid?: string; first_name: string; last_name: string; email: string; @@ -174,32 +174,13 @@ export type DATASQUIREL_LoggedInUser = { username?: string; image?: string; image_thumbnail?: string; - address?: string; - city?: string; - state?: string; - country?: string; - zip_code?: string; social_login?: number; social_platform?: string; social_id?: string; - more_user_data?: string; verification_status?: number; - loan_officer_id?: number; - is_admin?: number; - admin_level?: number; - admin_permissions?: string; - uuid?: string; - temp_login_code?: string; - date_created?: string; - date_created_code?: number; - date_created_timestamp?: string; - date_updated?: string; - date_updated_code?: number; - date_updated_timestamp?: string; - csrf_k?: string; - logged_in_status?: boolean; - date?: number; - more_data?: any; + csrf_k: string; + logged_in_status: boolean; + date: number; } & { [key: string]: any; }; @@ -208,6 +189,7 @@ export interface AuthenticatedUser { payload: DATASQUIREL_LoggedInUser | null; msg?: string; userId?: number; + cookieNames?: any; } export interface SuccessUserObject { id: number; @@ -322,25 +304,7 @@ export interface PostInsertReturn { protocol41: boolean; changedRows: number; } -export interface UserType { - id: number; - stripe_id?: string; - first_name: string; - last_name: string; - email: string; - bio?: string; - username?: string; - image: string; - image_thumbnail: string; - social_id?: string; - verification_status?: number; - social_platform?: string; - social_login?: number; - date?: number; - phone?: number | string; - csrf_k: string; - logged_in_status: boolean; -} +export type UserType = DATASQUIREL_LoggedInUser; export interface ApiKeyDef { name: string; scope: string; @@ -1061,6 +1025,10 @@ export type APILoginFunctionReturn = { msg?: string; payload?: DATASQUIREL_LoggedInUser | null; userId?: number | string; + key?: string; + token?: string; + csrf?: string; + cookieNames?: any; }; export type APILoginFunction = (params: APILoginFunctionParams) => Promise; export type APICreateUserFunctionParams = { @@ -1085,14 +1053,9 @@ export type APIGetUserFunction = (params: APIGetUserFunctionParams) => Promise} - Response object */ -export type HandleSocialDbFunction = (params: HandleSocialDbFunctionParams) => Promise; +export type HandleSocialDbFunction = (params: HandleSocialDbFunctionParams) => Promise; export type ApiReauthUserReturn = { success: boolean; payload?: { @@ -1146,4 +1108,91 @@ export type ApiReauthUserReturn = { msg?: string; userId?: string | number; }; +export type GoogleAccessTokenObject = { + access_token: string; + token_type: "Bearer"; + expires_in: number; + scope: string; + authuser: string; + prompt: string; +}; +export type GoogleOauth2User = { + sub: string; + name: string; + given_name: string; + family_name: string; + picture: string; + email: string; + email_verified: boolean; +}; +export interface AceEditorOptions { + animatedScroll?: boolean; + autoScrollEditorIntoView?: boolean; + behavioursEnabled?: boolean; + copyWithEmptySelection?: boolean; + cursorStyle?: "ace" | "slim" | "smooth" | "wide"; + customScrollbar?: boolean; + displayIndentGuides?: boolean; + dragDelay?: number; + dragEnabled?: boolean; + enableAutoIndent?: boolean; + enableBasicAutocompletion?: boolean | any[]; + enableKeyboardAccessibility?: boolean; + enableLiveAutocompletion?: boolean | any[]; + enableMobileMenu?: boolean; + enableMultiselect?: boolean; + enableSnippets?: boolean; + fadeFoldWidgets?: boolean; + firstLineNumber?: number; + fixedWidthGutter?: boolean; + focusTimeout?: number; + foldStyle?: "markbegin" | "markbeginend" | "manual"; + fontFamily?: string; + fontSize?: number; + hScrollBarAlwaysVisible?: boolean; + hasCssTransforms?: boolean; + highlightActiveLine?: boolean; + highlightGutterLine?: boolean; + highlightIndentGuides?: boolean; + highlightSelectedWord?: boolean; + indentedSoftWrap?: boolean; + keyboardHandler?: string; + liveAutocompletionDelay?: number; + liveAutocompletionThreshold?: number; + maxLines?: number; + maxPixelHeight?: number; + mergeUndoDeltas?: boolean | "always"; + minLines?: number; + mode?: string; + navigateWithinSoftTabs?: boolean; + newLineMode?: AceAjax.NewLineMode; + overwrite?: boolean; + placeholder?: string; + printMargin?: number | boolean; + printMarginColumn?: number; + readOnly?: boolean; + relativeLineNumbers?: boolean; + scrollPastEnd?: number; + scrollSpeed?: number; + selectionStyle?: string; + session?: any; + showFoldWidgets?: boolean; + showFoldedAnnotations?: boolean; + showGutter?: boolean; + showInvisibles?: boolean; + showLineNumbers?: boolean; + showPrintMargin?: boolean; + tabSize?: number; + textInputAriaLabel?: string; + theme?: string; + tooltipFollowsMouse?: boolean; + useSoftTabs?: boolean; + useSvgGutterIcons?: boolean; + useWorker?: boolean; + vScrollBarAlwaysVisible?: boolean; + value?: string; + wrap?: number | boolean | "off" | "free" | "printmargin"; + wrapBehavioursEnabled?: boolean; + wrapMethod?: "code" | "text" | "auto"; +} export {}; diff --git a/package-shared/types/index.ts b/package-shared/types/index.ts index 25f4a35..5250930 100644 --- a/package-shared/types/index.ts +++ b/package-shared/types/index.ts @@ -3,12 +3,6 @@ import type { IncomingMessage, ServerResponse } from "http"; import { Editor } from "tinymce"; export type DSQL_DatabaseFullName = string; -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - export interface DSQL_DatabaseSchemaType { dbName: string; dbSlug: string; @@ -26,8 +20,6 @@ export interface DSQL_ChildrenDatabaseObject { dbFullName: string; } -//////////////////////////////////////// - export interface DSQL_TableSchemaType { tableName: string; tableFullName: string; @@ -48,8 +40,6 @@ export interface DSQL_ChildrenTablesType { tableNameFull?: string; } -//////////////////////////////////////// - export interface DSQL_FieldSchemaType { fieldName?: string; originName?: string; @@ -92,8 +82,6 @@ export interface DSQL_ForeignKeyType { cascadeUpdate?: boolean; } -//////////////////////////////////////// - export interface DSQL_IndexSchemaType { indexName?: string; indexType?: string; @@ -118,8 +106,6 @@ export interface DSQL_MYSQL_SHOW_INDEXES_Type { Comment: string; } -//////////////////////////////////////// - export interface DSQL_MYSQL_SHOW_COLUMNS_Type { Field: string; Type: string; @@ -129,16 +115,12 @@ export interface DSQL_MYSQL_SHOW_COLUMNS_Type { Extra: string; } -//////////////////////////////////////// - export interface DSQL_MYSQL_FOREIGN_KEYS_Type { CONSTRAINT_NAME: string; CONSTRAINT_SCHEMA: string; TABLE_NAME: string; } -//////////////////////////////////////// - export interface DSQL_MYSQL_user_databases_Type { id: number; user_id: number; @@ -215,7 +197,8 @@ export interface SerializeQueryParams { // @ts-check export type DATASQUIREL_LoggedInUser = { - id?: number; + id: number; + uuid?: string; first_name: string; last_name: string; email: string; @@ -224,32 +207,13 @@ export type DATASQUIREL_LoggedInUser = { username?: string; image?: string; image_thumbnail?: string; - address?: string; - city?: string; - state?: string; - country?: string; - zip_code?: string; social_login?: number; social_platform?: string; social_id?: string; - more_user_data?: string; verification_status?: number; - loan_officer_id?: number; - is_admin?: number; - admin_level?: number; - admin_permissions?: string; - uuid?: string; - temp_login_code?: string; - date_created?: string; - date_created_code?: number; - date_created_timestamp?: string; - date_updated?: string; - date_updated_code?: number; - date_updated_timestamp?: string; - csrf_k?: string; - logged_in_status?: boolean; - date?: number; - more_data?: any; + csrf_k: string; + logged_in_status: boolean; + date: number; } & { [key: string]: any; }; @@ -259,6 +223,7 @@ export interface AuthenticatedUser { payload: DATASQUIREL_LoggedInUser | null; msg?: string; userId?: number; + cookieNames?: any; } export interface SuccessUserObject { @@ -391,25 +356,7 @@ export interface PostInsertReturn { changedRows: number; } -export interface UserType { - id: number; - stripe_id?: string; - first_name: string; - last_name: string; - email: string; - bio?: string; - username?: string; - image: string; - image_thumbnail: string; - social_id?: string; - verification_status?: number; - social_platform?: string; - social_login?: number; - date?: number; - phone?: number | string; - csrf_k: string; - logged_in_status: boolean; -} +export type UserType = DATASQUIREL_LoggedInUser; export interface ApiKeyDef { name: string; @@ -1278,6 +1225,10 @@ export type APILoginFunctionReturn = { msg?: string; payload?: DATASQUIREL_LoggedInUser | null; userId?: number | string; + key?: string; + token?: string; + csrf?: string; + cookieNames?: any; }; export type APILoginFunction = ( params: APILoginFunctionParams @@ -1313,12 +1264,9 @@ export type APIGetUserFunction = ( * API Google Login Function */ export type APIGoogleLoginFunctionParams = { - clientId: string; token: string; database: string; - userId: string | number; - additionalFields?: { [key: string]: any }; - res: any; + additionalFields?: string[]; }; export type APIGoogleLoginFunctionReturn = { @@ -1338,16 +1286,15 @@ export type HandleSocialDbFunctionParams = { email: string; social_platform: string; payload: any; - res?: ServerResponse; invitation?: any; supEmail?: string; - additionalFields?: object; + additionalFields?: string[]; useLocal?: boolean; }; export type HandleSocialDbFunctionReturn = { success: boolean; - user?: null; + user?: DATASQUIREL_LoggedInUser | null; msg?: string; social_id?: string | number; social_platform?: string; @@ -1372,7 +1319,7 @@ export type HandleSocialDbFunctionReturn = { */ export type HandleSocialDbFunction = ( params: HandleSocialDbFunctionParams -) => Promise; +) => Promise; export type ApiReauthUserReturn = { success: boolean; @@ -1380,3 +1327,93 @@ export type ApiReauthUserReturn = { msg?: string; userId?: string | number; }; + +export type GoogleAccessTokenObject = { + access_token: string; + token_type: "Bearer"; + expires_in: number; + scope: string; + authuser: string; + prompt: string; +}; + +export type GoogleOauth2User = { + sub: string; + name: string; + given_name: string; + family_name: string; + picture: string; + email: string; + email_verified: boolean; +}; + +export interface AceEditorOptions { + animatedScroll?: boolean; + autoScrollEditorIntoView?: boolean; + behavioursEnabled?: boolean; + copyWithEmptySelection?: boolean; + cursorStyle?: "ace" | "slim" | "smooth" | "wide"; + customScrollbar?: boolean; + displayIndentGuides?: boolean; + dragDelay?: number; + dragEnabled?: boolean; + enableAutoIndent?: boolean; + enableBasicAutocompletion?: boolean | any[]; + enableKeyboardAccessibility?: boolean; + enableLiveAutocompletion?: boolean | any[]; + enableMobileMenu?: boolean; + enableMultiselect?: boolean; + enableSnippets?: boolean; + fadeFoldWidgets?: boolean; + firstLineNumber?: number; + fixedWidthGutter?: boolean; + focusTimeout?: number; + foldStyle?: "markbegin" | "markbeginend" | "manual"; + fontFamily?: string; + fontSize?: number; + hScrollBarAlwaysVisible?: boolean; + hasCssTransforms?: boolean; + highlightActiveLine?: boolean; + highlightGutterLine?: boolean; + highlightIndentGuides?: boolean; + highlightSelectedWord?: boolean; + indentedSoftWrap?: boolean; + keyboardHandler?: string; + liveAutocompletionDelay?: number; + liveAutocompletionThreshold?: number; + maxLines?: number; + maxPixelHeight?: number; + mergeUndoDeltas?: boolean | "always"; + minLines?: number; + mode?: string; + navigateWithinSoftTabs?: boolean; + newLineMode?: AceAjax.NewLineMode; + overwrite?: boolean; + placeholder?: string; + printMargin?: number | boolean; + printMarginColumn?: number; + readOnly?: boolean; + relativeLineNumbers?: boolean; + scrollPastEnd?: number; + scrollSpeed?: number; + selectionStyle?: string; + session?: any; + showFoldWidgets?: boolean; + showFoldedAnnotations?: boolean; + showGutter?: boolean; + showInvisibles?: boolean; + showLineNumbers?: boolean; + showPrintMargin?: boolean; + tabSize?: number; + textInputAriaLabel?: string; + theme?: string; + tooltipFollowsMouse?: boolean; + useSoftTabs?: boolean; + useSvgGutterIcons?: boolean; + useWorker?: boolean; + vScrollBarAlwaysVisible?: boolean; + value?: string; + wrap?: number | boolean | "off" | "free" | "printmargin"; + wrapBehavioursEnabled?: boolean; + wrapMethod?: "code" | "text" | "auto"; +} diff --git a/package.json b/package.json index 1d43987..2cfdc2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/datasquirel", - "version": "2.7.7", + "version": "2.7.8", "description": "Cloud-based SQL data management tool", "main": "index.js", "bin": { diff --git a/users/add-user.d.ts b/users/add-user.d.ts index 180886b..18cd752 100644 --- a/users/add-user.d.ts +++ b/users/add-user.d.ts @@ -5,7 +5,7 @@ export = addUser; * @async * * @param {object} param - Single object passed - * @param {string} param.key - FULL ACCESS API Key + * @param {string} [param.key] - FULL ACCESS API Key * @param {string} param.database - Database Name * @param {import("../package-shared/types").UserDataPayload} param.payload - User Data Payload * @param {string} [param.encryptionKey] @@ -17,7 +17,7 @@ export = addUser; * @returns { Promise } */ declare function addUser({ key, payload, database, encryptionKey, user_id, useLocal, apiUserId, }: { - key: string; + key?: string; database: string; payload: import("../package-shared/types").UserDataPayload; encryptionKey?: string; diff --git a/users/add-user.js b/users/add-user.js index e972311..6ea6beb 100644 --- a/users/add-user.js +++ b/users/add-user.js @@ -11,7 +11,7 @@ const apiCreateUser = require("../package-shared/functions/api/users/api-create- * @async * * @param {object} param - Single object passed - * @param {string} param.key - FULL ACCESS API Key + * @param {string} [param.key] - FULL ACCESS API Key * @param {string} param.database - Database Name * @param {import("../package-shared/types").UserDataPayload} param.payload - User Data Payload * @param {string} [param.encryptionKey] diff --git a/users/delete-user.d.ts b/users/delete-user.d.ts new file mode 100644 index 0000000..a905cf8 --- /dev/null +++ b/users/delete-user.d.ts @@ -0,0 +1,21 @@ +export = deleteUser; +/** + * # Update User + * @async + * + * @param {object} params - API Key + * @param {String} [params.key] - API Key + * @param {String} params.database - Target Database + * @param {String | number} params.deletedUserId - Target Database + * @param {boolean} [params.user_id] - User ID + * @param {boolean} [params.useLocal] + * + * @returns { Promise} + */ +declare function deleteUser({ key, database, user_id, useLocal, deletedUserId }: { + key?: string; + database: string; + deletedUserId: string | number; + user_id?: boolean; + useLocal?: boolean; +}): Promise; diff --git a/users/delete-user.js b/users/delete-user.js new file mode 100644 index 0000000..217ad72 --- /dev/null +++ b/users/delete-user.js @@ -0,0 +1,127 @@ +// @ts-check + +const http = require("http"); +const https = require("https"); +const path = require("path"); +const fs = require("fs"); +const grabHostNames = require("../package-shared/utils/grab-host-names"); +const apiUpdateUser = require("../package-shared/functions/api/users/api-update-user"); +const apiDeleteUser = require("../package-shared/functions/api/users/api-delete-user"); + +/** + * # Update User + * @async + * + * @param {object} params - API Key + * @param {String} [params.key] - API Key + * @param {String} params.database - Target Database + * @param {String | number} params.deletedUserId - Target Database + * @param {boolean} [params.user_id] - User ID + * @param {boolean} [params.useLocal] + * + * @returns { Promise} + */ +async function deleteUser({ key, database, user_id, useLocal, deletedUserId }) { + /** + * Check for local DB settings + * + * @description Look for local db settings in `.env` file and by pass the http request if available + */ + const { DSQL_HOST, DSQL_USER, DSQL_PASS, DSQL_DB_NAME } = process.env; + + const grabedHostNames = grabHostNames(); + const { host, port, scheme } = grabedHostNames; + + if ( + DSQL_HOST?.match(/./) && + DSQL_USER?.match(/./) && + DSQL_PASS?.match(/./) && + DSQL_DB_NAME?.match(/./) && + useLocal + ) { + /** @type {import("../package-shared/types").DSQL_DatabaseSchemaType | undefined} */ + let dbSchema; + + try { + const localDbSchemaPath = path.resolve( + process.cwd(), + "dsql.schema.json" + ); + dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8")); + } catch (error) {} + + if (dbSchema) { + return await apiDeleteUser({ + dbFullName: DSQL_DB_NAME, + useLocal, + deletedUserId, + }); + } + } + + /** + * Make https request + * + * @description make a request to datasquirel.com + */ + const httpResponse = await new Promise((resolve, reject) => { + const reqPayload = JSON.stringify({ + database, + deletedUserId, + }); + + const httpsRequest = scheme.request( + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.from(reqPayload).length, + Authorization: + process.env.DSQL_FULL_ACCESS_API_KEY || + process.env.DSQL_API_KEY || + key, + }, + port, + hostname: host, + path: `/api/user/${ + user_id || grabedHostNames.user_id + }/delete-user`, + }, + + /** + * Callback Function + * + * @description https request callback + */ + (response) => { + var str = ""; + + response.on("data", function (chunk) { + str += chunk; + }); + + response.on("end", function () { + resolve(JSON.parse(str)); + }); + + response.on("error", (err) => { + reject(err); + }); + } + ); + httpsRequest.write(reqPayload); + httpsRequest.end(); + }); + + /** ********************************************** */ + /** ********************************************** */ + /** ********************************************** */ + + return httpResponse; +} + +/** ********************************************** */ +/** ********************************************** */ +/** ********************************************** */ + +module.exports = deleteUser; diff --git a/users/login-user.d.ts b/users/login-user.d.ts index 2d301a8..44819af 100644 --- a/users/login-user.d.ts +++ b/users/login-user.d.ts @@ -5,7 +5,7 @@ export = loginUser; * @async * * @param {object} params - Single Param object containing params - * @param {String} params.key - FULL ACCESS API Key + * @param {String} [params.key] - FULL ACCESS API Key * @param {String} params.database - Target Database * @param {{ * email?: string, @@ -13,9 +13,9 @@ export = loginUser; * password: string, * }} params.payload Login Email/Username and Password * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object - * @param {http.ServerResponse} params.response - Http response object - * @param {String} params.encryptionKey - Encryption Key - * @param {String} params.encryptionSalt - Encryption Salt + * @param {http.ServerResponse & Object} [params.response] - Http response object + * @param {String} [params.encryptionKey] - Encryption Key + * @param {String} [params.encryptionSalt] - Encryption Salt * @param {boolean} [params.email_login] - Email only Login * @param {string} [params.email_login_code] - Email login code * @param {string} [params.temp_code_field] - Database table field name for temporary code @@ -23,11 +23,12 @@ export = loginUser; * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.skipPassword] * @param {boolean} [params.useLocal] + * @param {string | number} [params.apiUserID] - Required for setting of cookies * - * @returns { Promise} + * @returns { Promise} */ -declare function loginUser({ key, payload, database, additionalFields, response, encryptionKey, encryptionSalt, email_login, email_login_code, temp_code_field, token, user_id, skipPassword, useLocal, }: { - key: string; +declare function loginUser({ key, payload, database, additionalFields, response, encryptionKey, encryptionSalt, email_login, email_login_code, temp_code_field, token, user_id, skipPassword, useLocal, apiUserID, }: { + key?: string; database: string; payload: { email?: string; @@ -35,9 +36,11 @@ declare function loginUser({ key, payload, database, additionalFields, response, password: string; }; additionalFields?: string[]; - response: http.ServerResponse; - encryptionKey: string; - encryptionSalt: string; + response?: http.ServerResponse & { + [x: string]: any; + }; + encryptionKey?: string; + encryptionSalt?: string; email_login?: boolean; email_login_code?: string; temp_code_field?: string; @@ -45,5 +48,6 @@ declare function loginUser({ key, payload, database, additionalFields, response, user_id?: boolean; skipPassword?: boolean; useLocal?: boolean; -}): Promise; + apiUserID?: string | number; +}): Promise; import http = require("http"); diff --git a/users/login-user.js b/users/login-user.js index b5287fe..771a905 100644 --- a/users/login-user.js +++ b/users/login-user.js @@ -13,6 +13,9 @@ const encrypt = require("../package-shared/functions/dsql/encrypt"); const grabHostNames = require("../package-shared/utils/grab-host-names"); const apiLoginUser = require("../package-shared/functions/api/users/api-login"); const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); +const { + writeAuthFile, +} = require("../package-shared/functions/backend/auth/write-auth-files"); /** * Login A user @@ -20,7 +23,7 @@ const getAuthCookieNames = require("../package-shared/functions/backend/cookies/ * @async * * @param {object} params - Single Param object containing params - * @param {String} params.key - FULL ACCESS API Key + * @param {String} [params.key] - FULL ACCESS API Key * @param {String} params.database - Target Database * @param {{ * email?: string, @@ -28,9 +31,9 @@ const getAuthCookieNames = require("../package-shared/functions/backend/cookies/ * password: string, * }} params.payload Login Email/Username and Password * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object - * @param {http.ServerResponse} params.response - Http response object - * @param {String} params.encryptionKey - Encryption Key - * @param {String} params.encryptionSalt - Encryption Salt + * @param {http.ServerResponse & Object} [params.response] - Http response object + * @param {String} [params.encryptionKey] - Encryption Key + * @param {String} [params.encryptionSalt] - Encryption Salt * @param {boolean} [params.email_login] - Email only Login * @param {string} [params.email_login_code] - Email login code * @param {string} [params.temp_code_field] - Database table field name for temporary code @@ -38,8 +41,9 @@ const getAuthCookieNames = require("../package-shared/functions/backend/cookies/ * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.skipPassword] * @param {boolean} [params.useLocal] + * @param {string | number} [params.apiUserID] - Required for setting of cookies * - * @returns { Promise} + * @returns { Promise} */ async function loginUser({ key, @@ -56,6 +60,7 @@ async function loginUser({ user_id, skipPassword, useLocal, + apiUserID, }) { const grabedHostNames = grabHostNames(); const { host, port, scheme } = grabedHostNames; @@ -67,6 +72,28 @@ async function loginUser({ : defaultTempLoginFieldName : undefined; + const finalEncryptionKey = + encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; + const finalEncryptionSalt = + encryptionSalt || process.env.DSQL_ENCRYPTION_SALT; + + if (!finalEncryptionKey?.match(/.{8,}/)) { + console.log("Encryption key is invalid"); + return { + success: false, + payload: null, + msg: "Encryption key is invalid", + }; + } + if (!finalEncryptionSalt?.match(/.{8,}/)) { + console.log("Encryption salt is invalid"); + return { + success: false, + payload: null, + msg: "Encryption salt is invalid", + }; + } + /** * Check required fields * @@ -80,43 +107,14 @@ async function loginUser({ }; } - /** - * Check Encryption Keys - * - * @description Check Encryption Keys - */ - if (!encryptionKey?.match(/./)) - return { - success: false, - payload: null, - msg: "Encryption Key Required", - }; - - if (!encryptionSalt?.match(/./)) - return { - success: false, - payload: null, - msg: "Encryption Salt Required", - }; - - if (encryptionKey.length < 24) - return { - success: false, - payload: null, - msg: "Encryption Key must be at least 24 characters", - }; - - if (encryptionSalt.length < 8) - return { - success: false, - payload: null, - msg: "Encryption Salt must be at least 8 characters", - }; - /** * Initialize HTTP response variable */ - let httpResponse; + + /** @type {import("../package-shared/types").APILoginFunctionReturn} */ + let httpResponse = { + success: false, + }; /** * Check for local DB settings @@ -150,7 +148,7 @@ async function loginUser({ username: payload.username, password: payload.password, skipPassword, - encryptionKey, + encryptionKey: finalEncryptionKey, additionalFields, email_login, email_login_code, @@ -170,7 +168,7 @@ async function loginUser({ httpResponse = await new Promise((resolve, reject) => { /** @type {import("../package-shared/types").PackageUserLoginRequestBody} */ const reqPayload = { - encryptionKey, + encryptionKey: finalEncryptionKey, payload, database, additionalFields, @@ -236,22 +234,34 @@ async function loginUser({ if (httpResponse?.success) { let encryptedPayload = encrypt({ data: JSON.stringify(httpResponse.payload), - encryptionKey, - encryptionSalt, + encryptionKey: finalEncryptionKey, + encryptionSalt: finalEncryptionSalt, }); try { - if (token) httpResponse["token"] = encryptedPayload; + if (token && encryptedPayload) + httpResponse["token"] = encryptedPayload; } catch (error) {} - const { userId } = httpResponse; + const cookieNames = getAuthCookieNames({ + database, + userId: apiUserID || process.env.DSQL_API_USER_ID, + }); - const cookieNames = getAuthCookieNames(); + if (httpResponse.csrf) { + writeAuthFile( + httpResponse.csrf, + JSON.stringify(httpResponse.payload) + ); + } + + httpResponse["cookieNames"] = cookieNames; + httpResponse["key"] = String(encryptedPayload); const authKeyName = cookieNames.keyCookieName; const csrfName = cookieNames.csrfCookieName; - response.setHeader("Set-Cookie", [ + response?.setHeader("Set-Cookie", [ `${authKeyName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, `${csrfName}=${httpResponse.payload?.csrf_k};samesite=strict;path=/;HttpOnly=true`, ]); diff --git a/users/logout-user.d.ts b/users/logout-user.d.ts index 47274a4..03aaf57 100644 --- a/users/logout-user.d.ts +++ b/users/logout-user.d.ts @@ -3,20 +3,23 @@ export = logoutUser; * Logout user * ============================================================================== * @param {object} params - Single Param object containing params - * @param {http.IncomingMessage} params.request - Http request object - * @param {http.ServerResponse} params.response - Http response object - * @param {string} [params.database] - Target database name(slug): optional => If you don't - * include this you will be logged out of all datasquirel websites instead of just the target - * database + * @param {string} params.encryptedUserString - Encrypted User String + * @param {http.ServerResponse & Object} [params.response] - Http response object + * @param {string} [params.database] - Target database name(slug): optional + * @param {string | number} [params.dsqlUserId] * - * @returns {{success: boolean, payload: string}} + * @returns {{success: boolean, payload: string, cookieNames?: any}} */ -declare function logoutUser({ request, response, database }: { - request: http.IncomingMessage; - response: http.ServerResponse; +declare function logoutUser({ response, database, dsqlUserId, encryptedUserString }: { + encryptedUserString: string; + response?: http.ServerResponse & { + [x: string]: any; + }; database?: string; + dsqlUserId?: string | number; }): { success: boolean; payload: string; + cookieNames?: any; }; import http = require("http"); diff --git a/users/logout-user.js b/users/logout-user.js index 0c4bfed..c91a70a 100644 --- a/users/logout-user.js +++ b/users/logout-user.js @@ -1,72 +1,61 @@ // @ts-check const http = require("http"); -const parseCookies = require("../utils/functions/parseCookies"); const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); +const decrypt = require("../package-shared/functions/dsql/decrypt"); +const EJSON = require("../package-shared/utils/ejson"); +const { + deleteAuthFile, +} = require("../package-shared/functions/backend/auth/write-auth-files"); /** * Logout user * ============================================================================== * @param {object} params - Single Param object containing params - * @param {http.IncomingMessage} params.request - Http request object - * @param {http.ServerResponse} params.response - Http response object - * @param {string} [params.database] - Target database name(slug): optional => If you don't - * include this you will be logged out of all datasquirel websites instead of just the target - * database + * @param {string} params.encryptedUserString - Encrypted User String + * @param {http.ServerResponse & Object} [params.response] - Http response object + * @param {string} [params.database] - Target database name(slug): optional + * @param {string | number} [params.dsqlUserId] * - * @returns {{success: boolean, payload: string}} + * @returns {{success: boolean, payload: string, cookieNames?: any}} */ -function logoutUser({ request, response, database }) { +function logoutUser({ response, database, dsqlUserId, encryptedUserString }) { /** * Check Encryption Keys * * @description Check Encryption Keys */ try { - const cookies = parseCookies({ request }); - const cookiesKeys = Object.keys(cookies); - - const keyNames = getAuthCookieNames(); - - const keyRegexp = new RegExp(keyNames.keyCookieName); - const csrfRegexp = new RegExp(keyNames.csrfCookieName); - - const authKeyName = cookiesKeys.filter((cookieKey) => - cookieKey.match(keyRegexp) - )[0]; - const csrfName = cookiesKeys.filter((cookieKey) => - cookieKey.match(csrfRegexp) - )[0]; - - if (authKeyName && csrfName) { - response.setHeader("Set-Cookie", [ - `${authKeyName}=null;max-age=0`, - `${csrfName}=null;max-age=0`, - ]); - } else { - const allKeys = cookiesKeys.filter((cookieKey) => - cookieKey.match(/datasquirel_.*_auth_key/) - ); - const allCsrfs = cookiesKeys.filter((cookieKey) => - cookieKey.match(/datasquirel_.*_csrf/) + const decryptedUserJSON = decrypt({ + encryptedString: encryptedUserString, + }); + const userObject = + /** @type {import("../package-shared/types").DATASQUIREL_LoggedInUser | undefined} */ ( + EJSON.parse(decryptedUserJSON) ); - response.setHeader("Set-Cookie", [ - ...allKeys.map( - (key) => - `${key}=null;samesite=strict;path=/;HttpOnly=true;Secure=true` - ), - ...allCsrfs.map( - (csrf) => - `${csrf}=null;samesite=strict;path=/;HttpOnly=true` - ), - `dsqluid=null;samesite=strict;path=/;HttpOnly=true`, - ]); - } + if (!userObject?.csrf_k) + throw new Error("Invalid User. Please check key"); + + const cookieNames = getAuthCookieNames({ + database, + userId: dsqlUserId || process.env.DSQL_API_USER_ID, + }); + const authKeyName = cookieNames.keyCookieName; + const csrfName = cookieNames.csrfCookieName; + + response?.setHeader("Set-Cookie", [ + `${authKeyName}=null;max-age=0`, + `${csrfName}=null;max-age=0`, + ]); + + const csrf = userObject.csrf_k; + deleteAuthFile(csrf); return { success: true, payload: "User Logged Out", + cookieNames, }; } catch (error) { console.log(error); diff --git a/users/reauth-user.d.ts b/users/reauth-user.d.ts index f3c2c90..de9b170 100644 --- a/users/reauth-user.d.ts +++ b/users/reauth-user.d.ts @@ -12,31 +12,31 @@ export = reauthUser; * @async * * @param {object} params - Single Param object containing params - * @param {String} params.key - API Key + * @param {String} [params.key] - API Key * @param {String} params.database - Target Database - * @param {http.ServerResponse} params.response - Http response object - * @param {http.IncomingMessage} params.request - Http request object + * @param {http.ServerResponse} [params.response] - Http response object + * @param {http.IncomingMessage} [params.request] - Http request object * @param {("deep" | "normal")} [params.level] - Authentication level - * @param {String} params.encryptionKey - Encryption Key - * @param {String} params.encryptionSalt - Encryption Salt + * @param {String} [params.encryptionKey] - Encryption Key + * @param {String} [params.encryptionSalt] - Encryption Salt * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object - * @param {string} [params.token] - access token to use instead of getting from cookie header + * @param {string} [params.encryptedUserString] - encrypted user string to use instead of getting from cookie header * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * - * @returns { Promise } + * @returns { Promise } */ -declare function reauthUser({ key, database, response, request, level, encryptionKey, encryptionSalt, additionalFields, token, user_id, useLocal, }: { - key: string; +declare function reauthUser({ key, database, response, request, level, encryptionKey, encryptionSalt, additionalFields, encryptedUserString, user_id, useLocal, }: { + key?: string; database: string; - response: http.ServerResponse; - request: http.IncomingMessage; + response?: http.ServerResponse; + request?: http.IncomingMessage; level?: ("deep" | "normal"); - encryptionKey: string; - encryptionSalt: string; + encryptionKey?: string; + encryptionSalt?: string; additionalFields?: string[]; - token?: string; + encryptedUserString?: string; user_id?: boolean; useLocal?: boolean; -}): Promise; +}): Promise; import http = require("http"); diff --git a/users/reauth-user.js b/users/reauth-user.js index 20b74ad..0403283 100644 --- a/users/reauth-user.js +++ b/users/reauth-user.js @@ -14,6 +14,11 @@ const encrypt = require("../package-shared/functions/dsql/encrypt"); const userAuth = require("./user-auth"); const grabHostNames = require("../package-shared/utils/grab-host-names"); const apiReauthUser = require("../package-shared/functions/api/users/api-reauth-user"); +const { + writeAuthFile, + deleteAuthFile, +} = require("../package-shared/functions/backend/auth/write-auth-files"); +const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -29,19 +34,19 @@ const apiReauthUser = require("../package-shared/functions/api/users/api-reauth- * @async * * @param {object} params - Single Param object containing params - * @param {String} params.key - API Key + * @param {String} [params.key] - API Key * @param {String} params.database - Target Database - * @param {http.ServerResponse} params.response - Http response object - * @param {http.IncomingMessage} params.request - Http request object + * @param {http.ServerResponse} [params.response] - Http response object + * @param {http.IncomingMessage} [params.request] - Http request object * @param {("deep" | "normal")} [params.level] - Authentication level - * @param {String} params.encryptionKey - Encryption Key - * @param {String} params.encryptionSalt - Encryption Salt + * @param {String} [params.encryptionKey] - Encryption Key + * @param {String} [params.encryptionSalt] - Encryption Salt * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object - * @param {string} [params.token] - access token to use instead of getting from cookie header + * @param {string} [params.encryptedUserString] - encrypted user string to use instead of getting from cookie header * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * - * @returns { Promise } + * @returns { Promise } */ async function reauthUser({ key, @@ -52,7 +57,7 @@ async function reauthUser({ encryptionKey, encryptionSalt, additionalFields, - token, + encryptedUserString, user_id, useLocal, }) { @@ -64,13 +69,18 @@ async function reauthUser({ const grabedHostNames = grabHostNames(); const { host, port, scheme } = grabedHostNames; + const finalEncryptionKey = + encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; + const finalEncryptionSalt = + encryptionSalt || process.env.DSQL_ENCRYPTION_SALT; + const existingUser = userAuth({ database, - encryptionKey, - encryptionSalt, + encryptionKey: finalEncryptionKey, + encryptionSalt: finalEncryptionSalt, level, request, - token, + encryptedUserString, }); if (!existingUser?.payload?.id) { @@ -189,23 +199,30 @@ async function reauthUser({ if (httpResponse?.success) { let encryptedPayload = encrypt({ data: JSON.stringify(httpResponse.payload), - encryptionKey, - encryptionSalt, + encryptionKey: finalEncryptionKey, + encryptionSalt: finalEncryptionSalt, }); const { userId } = httpResponse; + const cookieNames = getAuthCookieNames({ database, userId }); - const authKeyName = `datasquirel_${userId}_${database}_auth_key`; - const csrfName = `datasquirel_${userId}_${database}_csrf`; + httpResponse["cookieNames"] = cookieNames; + httpResponse["key"] = String(encryptedPayload); - response.setHeader("Set-Cookie", [ + const authKeyName = cookieNames.keyCookieName; + const csrfName = cookieNames.csrfCookieName; + + response?.setHeader("Set-Cookie", [ `${authKeyName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, - `${csrfName}=${httpResponse.payload.csrf_k};samesite=strict;path=/;HttpOnly=true`, - `dsqluid=${userId};samesite=strict;path=/;HttpOnly=true`, + `${csrfName}=${httpResponse.payload?.csrf_k};samesite=strict;path=/;HttpOnly=true`, ]); - if (token) { - httpResponse.token = encryptedPayload; + if (httpResponse.csrf) { + deleteAuthFile(String(existingUser.payload.csrf_k)); + writeAuthFile( + httpResponse.csrf, + JSON.stringify(httpResponse.payload) + ); } } diff --git a/users/social/github-auth.d.ts b/users/social/github-auth.d.ts index cfb13a6..87cab88 100644 --- a/users/social/github-auth.d.ts +++ b/users/social/github-auth.d.ts @@ -28,7 +28,7 @@ export = githubAuth; * @param {http.ServerResponse} params.response - HTTPS response object * @param {string} params.encryptionKey - Encryption key * @param {string} params.encryptionSalt - Encryption salt - * @param {object} [params.additionalFields] - Additional Fields to be added to the user object + * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object * @param {boolean} [params.user_id] - User ID * * @returns { Promise } @@ -43,7 +43,7 @@ declare function githubAuth({ key, code, email, database, clientId, clientSecret response: http.ServerResponse; encryptionKey: string; encryptionSalt: string; - additionalFields?: object; + additionalFields?: string[]; user_id?: boolean; }): Promise; declare namespace githubAuth { diff --git a/users/social/github-auth.js b/users/social/github-auth.js index 9b9fd74..894f553 100644 --- a/users/social/github-auth.js +++ b/users/social/github-auth.js @@ -44,7 +44,7 @@ const apiGithubLogin = require("../../package-shared/functions/api/users/social/ * @param {http.ServerResponse} params.response - HTTPS response object * @param {string} params.encryptionKey - Encryption key * @param {string} params.encryptionSalt - Encryption salt - * @param {object} [params.additionalFields] - Additional Fields to be added to the user object + * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object * @param {boolean} [params.user_id] - User ID * * @returns { Promise } diff --git a/users/social/google-auth.d.ts b/users/social/google-auth.d.ts index c8ef006..8241e71 100644 --- a/users/social/google-auth.d.ts +++ b/users/social/google-auth.d.ts @@ -19,29 +19,27 @@ export = googleAuth; * @async * * @param {object} params - main params object - * @param {string} params.key - API full access key + * @param {string} [params.key] - API full access key * @param {string} params.token - Google access token gotten from the client side * @param {string} params.database - Target database name(slug) - * @param {string} params.clientId - Google client id - * @param {http.ServerResponse} params.response - HTTPS response object - * @param {string} params.encryptionKey - Encryption key - * @param {string} params.encryptionSalt - Encryption salt - * @param {object} [params.additionalFields] - Additional Fields to be added to the user object + * @param {http.ServerResponse} [params.response] - HTTPS response object + * @param {string} [params.encryptionKey] - Encryption key + * @param {string} [params.encryptionSalt] - Encryption salt + * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object * @param {boolean} [params.user_id] - User ID - * @param {string | number} [params.apiUserID] - Required for Local + * @param {string | number} [params.apiUserID] - Required for setting of cookies * @param {boolean} [params.useLocal] - Whether to use a remote database instead of API * * @returns { Promise } */ -declare function googleAuth({ key, token, database, clientId, response, encryptionKey, encryptionSalt, additionalFields, user_id, apiUserID, useLocal, }: { - key: string; +declare function googleAuth({ key, token, database, response, encryptionKey, encryptionSalt, additionalFields, user_id, apiUserID, useLocal, }: { + key?: string; token: string; database: string; - clientId: string; - response: http.ServerResponse; - encryptionKey: string; - encryptionSalt: string; - additionalFields?: object; + response?: http.ServerResponse; + encryptionKey?: string; + encryptionSalt?: string; + additionalFields?: string[]; user_id?: boolean; apiUserID?: string | number; useLocal?: boolean; diff --git a/users/social/google-auth.js b/users/social/google-auth.js index e3104b1..3ab8221 100644 --- a/users/social/google-auth.js +++ b/users/social/google-auth.js @@ -6,12 +6,15 @@ * ============================================================================== */ const http = require("http"); -const https = require("https"); const fs = require("fs"); const path = require("path"); const encrypt = require("../../package-shared/functions/dsql/encrypt"); const grabHostNames = require("../../package-shared/utils/grab-host-names"); const apiGoogleLogin = require("../../package-shared/functions/api/users/social/api-google-login"); +const getAuthCookieNames = require("../../package-shared/functions/backend/cookies/get-auth-cookie-names"); +const { + writeAuthFile, +} = require("../../package-shared/functions/backend/auth/write-auth-files"); /** ****************************************************************************** */ /** ****************************************************************************** */ @@ -35,16 +38,15 @@ const apiGoogleLogin = require("../../package-shared/functions/api/users/social/ * @async * * @param {object} params - main params object - * @param {string} params.key - API full access key + * @param {string} [params.key] - API full access key * @param {string} params.token - Google access token gotten from the client side * @param {string} params.database - Target database name(slug) - * @param {string} params.clientId - Google client id - * @param {http.ServerResponse} params.response - HTTPS response object - * @param {string} params.encryptionKey - Encryption key - * @param {string} params.encryptionSalt - Encryption salt - * @param {object} [params.additionalFields] - Additional Fields to be added to the user object + * @param {http.ServerResponse} [params.response] - HTTPS response object + * @param {string} [params.encryptionKey] - Encryption key + * @param {string} [params.encryptionSalt] - Encryption salt + * @param {string[]} [params.additionalFields] - Additional Fields to be added to the user object * @param {boolean} [params.user_id] - User ID - * @param {string | number} [params.apiUserID] - Required for Local + * @param {string | number} [params.apiUserID] - Required for setting of cookies * @param {boolean} [params.useLocal] - Whether to use a remote database instead of API * * @returns { Promise } @@ -53,7 +55,6 @@ async function googleAuth({ key, token, database, - clientId, response, encryptionKey, encryptionSalt, @@ -65,18 +66,33 @@ async function googleAuth({ const grabedHostNames = grabHostNames(); const { host, port, scheme } = grabedHostNames; + const finalEncryptionKey = + encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; + const finalEncryptionSalt = + encryptionSalt || process.env.DSQL_ENCRYPTION_SALT; + + if (!finalEncryptionKey?.match(/.{8,}/)) { + console.log("Encryption key is invalid"); + return { + success: false, + payload: null, + msg: "Encryption key is invalid", + }; + } + if (!finalEncryptionSalt?.match(/.{8,}/)) { + console.log("Encryption salt is invalid"); + return { + success: false, + payload: null, + msg: "Encryption salt is invalid", + }; + } + /** * Check inputs * * @description Check inputs */ - if (!key || key?.match(/ /)) { - return { - success: false, - user: null, - msg: "Please enter API full access Key", - }; - } if (!token || token?.match(/ /)) { return { @@ -94,46 +110,14 @@ async function googleAuth({ }; } - if (!clientId || clientId?.match(/ /)) { - return { - success: false, - user: null, - msg: "Please enter Google OAUTH client ID", - }; - } - - if (!response || !response?.setHeader) { - return { - success: false, - user: null, - msg: "Please provide a valid HTTPS response object", - }; - } - - if (!encryptionKey || encryptionKey?.match(/ /)) { - return { - success: false, - user: null, - msg: "Please provide a valid encryption key", - }; - } - - if (!encryptionSalt || encryptionSalt?.match(/ /)) { - return { - success: false, - user: null, - msg: "Please provide a valid encryption salt", - }; - } - - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - /** * Initialize HTTP response variable */ - let httpResponse; + + /** @type {import("../../package-shared/types").APILoginFunctionReturn} */ + let httpResponse = { + success: false, + }; /** * Check for local DB settings @@ -163,18 +147,11 @@ async function googleAuth({ if (dbSchema && apiUserID) { httpResponse = await apiGoogleLogin({ token, - clientId, additionalFields, - res: response, database: DSQL_DB_NAME, - userId: apiUserID, }); } } else { - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - /** * Make https request * @@ -184,7 +161,6 @@ async function googleAuth({ httpResponse = await new Promise((resolve, reject) => { const reqPayload = JSON.stringify({ token, - clientId, database, additionalFields, }); @@ -233,44 +209,45 @@ async function googleAuth({ }); } - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - /** * Make https request * * @description make a request to datasquirel.com */ - if (httpResponse?.success && httpResponse?.user) { + if (httpResponse?.success && httpResponse?.payload) { let encryptedPayload = encrypt({ - data: JSON.stringify(httpResponse.user), - encryptionKey, - encryptionSalt, + data: JSON.stringify(httpResponse.payload), + encryptionKey: finalEncryptionKey, + encryptionSalt: finalEncryptionSalt, }); - const { user, dsqlUserId } = httpResponse; + const cookieNames = getAuthCookieNames({ + database, + userId: apiUserID || process.env.DSQL_API_USER_ID, + }); - const authKeyName = `datasquirel_${dsqlUserId}_${database}_auth_key`; - const csrfName = `datasquirel_${dsqlUserId}_${database}_csrf`; + console.log("apiUserID", apiUserID); - response.setHeader("Set-Cookie", [ + if (httpResponse.csrf) { + writeAuthFile( + httpResponse.csrf, + JSON.stringify(httpResponse.payload) + ); + } + + httpResponse["cookieNames"] = cookieNames; + httpResponse["key"] = String(encryptedPayload); + + const authKeyName = cookieNames.keyCookieName; + const csrfName = cookieNames.csrfCookieName; + + response?.setHeader("Set-Cookie", [ `${authKeyName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, - `${csrfName}=${user.csrf_k};samesite=strict;path=/;HttpOnly=true`, - `dsqluid=${dsqlUserId};samesite=strict;path=/;HttpOnly=true`, - `datasquirel_social_id=${user.social_id};samesite=strict;path=/`, + `${csrfName}=${httpResponse.payload?.csrf_k};samesite=strict;path=/;HttpOnly=true`, ]); } - //////////////////////////////////////// - //////////////////////////////////////// - //////////////////////////////////////// - return httpResponse; } -//////////////////////////////////////// -//////////////////////////////////////// -//////////////////////////////////////// - module.exports = googleAuth; diff --git a/users/update-user.d.ts b/users/update-user.d.ts index cc18071..cb8886b 100644 --- a/users/update-user.d.ts +++ b/users/update-user.d.ts @@ -4,20 +4,20 @@ export = updateUser; * @async * * @param {object} params - API Key - * @param {String} params.key - API Key + * @param {String} [params.key] - API Key * @param {String} params.database - Target Database - * @param {{ id: number } & Object.} params.payload - User Object: ID is required + * @param {String | number} params.updatedUserId - Target Database + * @param {Object.} params.payload - User Object: ID is required * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * * @returns { Promise} */ -declare function updateUser({ key, payload, database, user_id, useLocal }: { - key: string; +declare function updateUser({ key, payload, database, user_id, useLocal, updatedUserId, }: { + key?: string; database: string; + updatedUserId: string | number; payload: { - id: number; - } & { [x: string]: any; }; user_id?: boolean; diff --git a/users/update-user.js b/users/update-user.js index 2e70003..3ed61be 100644 --- a/users/update-user.js +++ b/users/update-user.js @@ -12,15 +12,23 @@ const apiUpdateUser = require("../package-shared/functions/api/users/api-update- * @async * * @param {object} params - API Key - * @param {String} params.key - API Key + * @param {String} [params.key] - API Key * @param {String} params.database - Target Database - * @param {{ id: number } & Object.} params.payload - User Object: ID is required + * @param {String | number} params.updatedUserId - Target Database + * @param {Object.} params.payload - User Object: ID is required * @param {boolean} [params.user_id] - User ID * @param {boolean} [params.useLocal] * * @returns { Promise} */ -async function updateUser({ key, payload, database, user_id, useLocal }) { +async function updateUser({ + key, + payload, + database, + user_id, + useLocal, + updatedUserId, +}) { /** * Check for local DB settings * @@ -54,6 +62,8 @@ async function updateUser({ key, payload, database, user_id, useLocal }) { payload: payload, dbFullName: DSQL_DB_NAME, useLocal, + updatedUserId, + dbSchema, }); } } @@ -67,6 +77,7 @@ async function updateUser({ key, payload, database, user_id, useLocal }) { const reqPayload = JSON.stringify({ payload, database, + updatedUserId, }); const httpsRequest = scheme.request( diff --git a/users/user-auth.d.ts b/users/user-auth.d.ts index 623730f..85c8c53 100644 --- a/users/user-auth.d.ts +++ b/users/user-auth.d.ts @@ -6,21 +6,31 @@ export = userAuth; * with the user's data * * @param {Object} params - Arg - * @param {http.IncomingMessage} params.request - Http request object - * @param {string} params.encryptionKey - Encryption Key - * @param {string} params.encryptionSalt - Encryption Salt + * @param {http.IncomingMessage & Object} [params.request] - Http request object + * @param {http.IncomingMessage & Object} [params.req] - Http request object + * @param {string} [params.encryptedUserString] - Encrypted user string to use instead of getting from cookie header + * @param {string} [params.encryptionKey] - Encryption Key: alt env: DSQL_ENCRYPTION_PASSWORD + * @param {string} [params.encryptionSalt] - Encryption Salt: alt env: DSQL_ENCRYPTION_SALT * @param {("deep" | "normal")} [params.level] - Optional. "Deep" value indicates an extra layer of security - * @param {string} params.database - Database Name - * @param {string} [params.token] - access token to use instead of getting from cookie header + * @param {string} [params.database] - Database Name (slug) + * @param {string | number} [params.dsqlUserId] - alt env: DSQL_API_USER_ID + * @param {number} [params.expiry] - Expiry time in milliseconds * * @returns { import("../package-shared/types").AuthenticatedUser } */ -declare function userAuth({ request, encryptionKey, encryptionSalt, level, database, token, }: { - request: http.IncomingMessage; - encryptionKey: string; - encryptionSalt: string; +declare function userAuth({ request, req, encryptionKey, encryptionSalt, level, database, dsqlUserId, encryptedUserString, expiry, }: { + request?: http.IncomingMessage & { + [x: string]: any; + }; + req?: http.IncomingMessage & { + [x: string]: any; + }; + encryptedUserString?: string; + encryptionKey?: string; + encryptionSalt?: string; level?: ("deep" | "normal"); - database: string; - token?: string; + database?: string; + dsqlUserId?: string | number; + expiry?: number; }): import("../package-shared/types").AuthenticatedUser; import http = require("http"); diff --git a/users/user-auth.js b/users/user-auth.js index 1fa2ebe..2e51aac 100644 --- a/users/user-auth.js +++ b/users/user-auth.js @@ -4,6 +4,16 @@ const http = require("http"); const decrypt = require("../package-shared/functions/dsql/decrypt"); const parseCookies = require("../utils/functions/parseCookies"); const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); +const { + checkAuthFile, +} = require("../package-shared/functions/backend/auth/write-auth-files"); + +const minuteInMilliseconds = 60000; +const hourInMilliseconds = minuteInMilliseconds * 60; +const dayInMilliseconds = hourInMilliseconds * 24; +const weekInMilliseconds = dayInMilliseconds * 7; +const monthInMilliseconds = dayInMilliseconds * 30; +const yearInMilliseconds = dayInMilliseconds * 365; /** * Authenticate User from request @@ -12,37 +22,48 @@ const getAuthCookieNames = require("../package-shared/functions/backend/cookies/ * with the user's data * * @param {Object} params - Arg - * @param {http.IncomingMessage} params.request - Http request object - * @param {string} params.encryptionKey - Encryption Key - * @param {string} params.encryptionSalt - Encryption Salt + * @param {http.IncomingMessage & Object} [params.request] - Http request object + * @param {http.IncomingMessage & Object} [params.req] - Http request object + * @param {string} [params.encryptedUserString] - Encrypted user string to use instead of getting from cookie header + * @param {string} [params.encryptionKey] - Encryption Key: alt env: DSQL_ENCRYPTION_PASSWORD + * @param {string} [params.encryptionSalt] - Encryption Salt: alt env: DSQL_ENCRYPTION_SALT * @param {("deep" | "normal")} [params.level] - Optional. "Deep" value indicates an extra layer of security - * @param {string} params.database - Database Name - * @param {string} [params.token] - access token to use instead of getting from cookie header + * @param {string} [params.database] - Database Name (slug) + * @param {string | number} [params.dsqlUserId] - alt env: DSQL_API_USER_ID + * @param {number} [params.expiry] - Expiry time in milliseconds * * @returns { import("../package-shared/types").AuthenticatedUser } */ function userAuth({ request, + req, encryptionKey, encryptionSalt, level, database, - token, + dsqlUserId, + encryptedUserString, + expiry = weekInMilliseconds, }) { try { - /** - * Grab the payload - * - * @description Grab the payload - */ - const cookies = parseCookies({ request }); + const finalEncryptionKey = + encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; + const finalEncryptionSalt = + encryptionSalt || process.env.DSQL_ENCRYPTION_SALT; - const keyNames = getAuthCookieNames(); + const cookies = parseCookies({ request: request || req }); + + const keyNames = getAuthCookieNames({ + userId: dsqlUserId || process.env.DSQL_API_USER_ID, + database: database || process.env.DSQL_DB_NAME, + }); const authKeyName = keyNames.keyCookieName; const csrfName = keyNames.csrfCookieName; - const key = token ? token : cookies[authKeyName]; + const key = encryptedUserString + ? encryptedUserString + : cookies[authKeyName]; const csrf = cookies[csrfName]; /** @@ -50,10 +71,10 @@ function userAuth({ * * @description Grab the payload */ - let userPayload = decrypt({ + let userPayloadJSON = decrypt({ encryptedString: key, - encryptionKey, - encryptionSalt, + encryptionKey: finalEncryptionKey, + encryptionSalt: finalEncryptionSalt, }); /** @@ -61,7 +82,7 @@ function userAuth({ * * @description Grab the payload */ - if (!userPayload) { + if (!userPayloadJSON) { return { success: false, payload: null, @@ -74,7 +95,9 @@ function userAuth({ * * @description Grab the payload */ - let userObject = JSON.parse(userPayload); + + /** @type {import("../package-shared/types").DATASQUIREL_LoggedInUser} */ + let userObject = JSON.parse(userPayloadJSON); if (!userObject.csrf_k) { return { @@ -84,6 +107,14 @@ function userAuth({ }; } + if (!checkAuthFile(userObject.csrf_k)) { + return { + success: false, + payload: null, + msg: "Auth file doesn't exist", + }; + } + /** * Grab the payload * @@ -100,6 +131,33 @@ function userAuth({ }; } + const payloadCreationDate = Number(userObject.date); + + if ( + Number.isNaN(payloadCreationDate) || + typeof payloadCreationDate !== "number" + ) { + return { + success: false, + payload: null, + msg: "Payload Creation Date is not a number", + }; + } + + const timeElapsed = Date.now() - payloadCreationDate; + + const finalExpiry = process.env.DSQL_SESSION_EXPIRY_TIME + ? Number(process.env.DSQL_SESSION_EXPIRY_TIME) + : expiry; + + if (timeElapsed > finalExpiry) { + return { + success: false, + payload: null, + msg: "Session has expired", + }; + } + /** * Return User Object * diff --git a/utils/functions/parseCookies.d.ts b/utils/functions/parseCookies.d.ts index 2b064f5..f1f2d07 100644 --- a/utils/functions/parseCookies.d.ts +++ b/utils/functions/parseCookies.d.ts @@ -1,5 +1,5 @@ declare function _exports({ request }: { - request: http.IncomingMessage; + request?: http.IncomingMessage; }): any | null; export = _exports; import http = require("http"); diff --git a/utils/functions/parseCookies.js b/utils/functions/parseCookies.js index 417847b..0c1210f 100644 --- a/utils/functions/parseCookies.js +++ b/utils/functions/parseCookies.js @@ -16,16 +16,12 @@ const http = require("http"); * @async * * @param {object} params - main params object - * @param {http.IncomingMessage} params.request - HTTPS request object + * @param {http.IncomingMessage} [params.request] - HTTPS request object * * @returns {* | null} */ module.exports = function ({ request }) { - /** - * Check inputs - * - * @description Check inputs - */ + if (!request) return {}; //////////////////////////////////////// //////////////////////////////////////// @@ -47,7 +43,10 @@ module.exports = function ({ request }) { cookieSplitArray.forEach((keyValueString) => { const [key, value] = keyValueString.split("="); if (key && typeof key == "string") { - cookieObject[key.replace(/^ +| +$/, "")] = value && typeof value == "string" ? value.replace(/^ +| +$/, "") : null; + cookieObject[key.replace(/^ +| +$/, "")] = + value && typeof value == "string" + ? value.replace(/^ +| +$/, "") + : null; } }); diff --git a/utils/get.d.ts b/utils/get.d.ts index 2fe526c..b18d2cb 100644 --- a/utils/get.d.ts +++ b/utils/get.d.ts @@ -11,7 +11,7 @@ export = get; * @param {string[]} [params.queryValues] - An array of query values if using "?" placeholders * @param {string} [params.tableName] - Name of the table to query * @param {boolean} [params.useLocal] - Whether to use a remote database instead of API - * @param {boolean} [params.user_id] - User ID + * @param {string | number} [params.user_id] - User ID * * @returns { Promise } - Return Object */ @@ -22,5 +22,5 @@ declare function get({ key, db, query, queryValues, tableName, useLocal, user_id queryValues?: string[]; tableName?: string; useLocal?: boolean; - user_id?: boolean; + user_id?: string | number; }): Promise; diff --git a/utils/get.js b/utils/get.js index 111dd42..9412628 100644 --- a/utils/get.js +++ b/utils/get.js @@ -19,7 +19,7 @@ const apiGet = require("../package-shared/functions/api/query/get"); * @param {string[]} [params.queryValues] - An array of query values if using "?" placeholders * @param {string} [params.tableName] - Name of the table to query * @param {boolean} [params.useLocal] - Whether to use a remote database instead of API - * @param {boolean} [params.user_id] - User ID + * @param {string | number} [params.user_id] - User ID * * @returns { Promise } - Return Object */ @@ -112,6 +112,8 @@ async function get({ path: encodeURI(path), }; + console.log("requestObject", requestObject); + scheme .request( requestObject,