This commit is contained in:
Tben
2023-08-13 14:00:04 +01:00
parent 98c4c17eb3
commit 43287b0329
25 changed files with 2106 additions and 230 deletions
+160
View File
@@ -0,0 +1,160 @@
// @ts-check
/**
* ==============================================================================
* Imports
* ==============================================================================
*/
const fs = require("fs");
const httpsRequest = require("./httpsRequest");
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
*
* @typedef {object} GithubUserPayload
* @property {string} login - Full name merged eg. "JohnDoe"
* @property {number} id - github user id
* @property {string} node_id - Some other id
* @property {string} avatar_url - profile picture
* @property {string} gravatar_id - some other id
* @property {string} url - Github user URL
* @property {string} html_url - User html URL - whatever that means
* @property {string} followers_url - Followers URL
* @property {string} following_url - Following URL
* @property {string} gists_url - Gists URL
* @property {string} starred_url - Starred URL
* @property {string} subscriptions_url - Subscriptions URL
* @property {string} organizations_url - Organizations URL
* @property {string} repos_url - Repositories URL
* @property {string} received_events_url - Received Events URL
* @property {string} type - Common value => "User"
* @property {boolean} site_admin - Is site admin or not? Boolean
* @property {string} name - More like "username"
* @property {string} company - User company
* @property {string} blog - User blog URL
* @property {string} location - User Location
* @property {string} email - User Email
* @property {string} hireable - Is user hireable
* @property {string} bio - User bio
* @property {string} twitter_username - User twitter username
* @property {number} public_repos - Number of public repositories
* @property {number} public_gists - Number of public gists
* @property {number} followers - Number of followers
* @property {number} following - Number of following
* @property {string} created_at - Date created
* @property {string} updated_at - Date updated
*/
/**
* Login/signup a github user
* ==============================================================================
* @async
*
* @param {Object} params - foundUser if any
* @param {string} params.code - github auth token
* @param {string} params.clientId - github client Id
* @param {string} params.clientSecret - github client Secret
*
* @returns {Promise<GithubUserPayload|null>}
*/
async function githubLogin({ code, clientId, clientSecret }) {
/** @type {GithubUserPayload | null} */
let gitHubUser = null;
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
try {
/**
* Create new user folder and file
*
* @description Create new user folder and file
*/
// const response = await fetch(`https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}`);
const response = await httpsRequest({
method: "POST",
hostname: "github.com",
path: `/login/oauth/access_token?client_id=${clientId}&client_secret=${clientSecret}&code=${code}`,
headers: {
Accept: "application/json",
"User-Agent": "*",
},
});
// `https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}&client_secret=${process.env.GITHUB_SECRET}&code=${code}`,
// body: JSON.stringify({
// client_id: process.env.GITHUB_ID,
// client_secret: process.env.GITHUB_SECRET,
// code: code,
// }),
const accessTokenObject = JSON.parse(response);
if (!accessTokenObject?.access_token) {
return gitHubUser;
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const userDataResponse = await httpsRequest({
method: "GET",
hostname: "api.github.com",
path: "/user",
headers: {
Authorization: `Bearer ${accessTokenObject.access_token}`,
"User-Agent": "*",
},
});
gitHubUser = JSON.parse(userDataResponse);
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
if (!gitHubUser?.email) {
const existingGithubUser = await global.DB_HANDLER(`SELECT email FROM users WHERE social_login='1' AND social_platform='github' AND social_id='${gitHubUser?.id || ""}'`);
if (existingGithubUser && existingGithubUser[0] && gitHubUser) {
gitHubUser.email = existingGithubUser[0].email;
}
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
} catch (error) {
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
console.log("ERROR in githubLogin.js backend function =>", error.message);
// serverError({
// component: "/api/social-login/github-auth/catch-error",
// message: error.message,
// user: user,
// });
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
return gitHubUser;
}
module.exports = githubLogin;
+142
View File
@@ -0,0 +1,142 @@
/**
* ==============================================================================
* Imports
* ==============================================================================
*/
const fs = require("fs");
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const { OAuth2Client } = require("google-auth-library");
const { hashPassword } = require("../passwordHash");
const serverError = require("../serverError");
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* ==============================================================================
* Main Function
* ==============================================================================
* @param {Object} params - foundUser if any
*/
module.exports = async function googleLogin({ usertype, foundUser, isSocialValidated, isUserValid, reqBody, serverRes, loginFailureReason }) {
const client = new OAuth2Client(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID);
let isGoogleAuthValid = false;
let newFoundUser = null;
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
try {
const ticket = await client.verifyIdToken({
idToken: reqBody.token,
audience: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.payload;
const userid = payload["sub"];
isUserValid = payload.email_verified;
if (!isUserValid || !payload || !payload.email_verified) return;
serverRes.isUserValid = payload.email_verified;
isSocialValidated = payload.email_verified;
isGoogleAuthValid = payload.email_verified;
////// If request specified a G Suite domain:
////// const domain = payload['hd'];
let socialHashedPassword = hashPassword(payload.jti);
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
let existinEmail = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE email='${payload.email}' AND social_login!='1' AND social_platform!='google'`);
if (existinEmail && existinEmail[0]) {
loginFailureReason = "Email Exists Already";
isGoogleAuthValid = false;
return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser, loginFailureReason: loginFailureReason };
}
////////////////////////////////////////
foundUser = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE email='${payload.email}' AND social_login='1' AND social_platform='google'`);
if (foundUser && foundUser[0]) {
newFoundUser = foundUser;
return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser };
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
let newUser = await global.DB_HANDLER(`INSERT INTO ${usertype} (
first_name,
last_name,
social_platform,
social_name,
social_id,
email,
image,
image_thumbnail,
password,
verification_status,
social_login,
terms_agreement,
date_created,
date_code
) VALUES (
'${payload.given_name}',
'${payload.family_name}',
'google',
'google_${payload.email.replace(/@.*/, "")}',
'${payload.sub}',
'${payload.email}',
'${payload.picture}',
'${payload.picture}',
'${socialHashedPassword}',
'1',
'1',
'1',
'${Date()}',
'${Date.now()}'
)`);
newFoundUser = await global.DB_HANDLER(`SELECT * FROM ${usertype} WHERE id='${newUser.insertId}'`);
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
} catch (error) {
serverError({
component: "googleLogin",
message: error.message,
user: {},
});
loginFailureReason = error;
isUserValid = false;
isSocialValidated = false;
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
return { isGoogleAuthValid: isGoogleAuthValid, newFoundUser: newFoundUser };
};
+377
View File
@@ -0,0 +1,377 @@
// @ts-check
/**
* ==============================================================================
* Imports
* ==============================================================================
*/
const fs = require("fs");
const http = require("http");
const varDatabaseDbHandler = require("../../../engine/utils/varDatabaseDbHandler");
const addDbEntry = require("../../../query/utils/addDbEntry");
const encrypt = require("../../../../functions/encrypt");
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* @typedef {object} FunctionReturn
* @property {boolean} success - Did the operation complete successfully or not?
* @property {{
* id: number,
* first_name: string,
* last_name: string,
* }|null} user - User payload object: or "null"
* @property {string} [msg] - Message
* @property {string} [error] - Error Message
* @property {string | number} [social_id] - Social Id
* @property {string} [social_platform] - Social Platform
* @property {object} [payload] - Payload
* @property {boolean} [alert] - Alert
* @property {*} [newUser] - New User
*/
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const database = process.env.DSQL_DB_NAME || "";
const encryptionKey = process.env.DSQL_ENCRYPTION_KEY || "";
const encryptionSalt = process.env.DSQL_ENCRYPTION_SALT || "";
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
/**
* Handle Social User Auth on Datasquirel Database
* ==============================================================================
*
* @description This function handles all social login logic after the social user
* has been authenticated and userpayload is present. The payload MUST contain the
* specified fields because this funciton will create a new user if the authenticated
* user does not exist.
*
* @param {{
* database: string|null|undefined,
* social_id: string|number,
* email: string,
* social_platform: string,
* payload: {
* social_id: string | number,
* email: string,
* social_platform: string,
* first_name: string,
* last_name: string,
* image: string,
* image_thumbnail: string,
* username: string,
* },
* res: object|null,
* invitation?: object|null,
* supEmail?: string | null,
* additionalFields?: object,
* dbSchema: import("../../../../types/database-schema.td").DSQL_DatabaseSchemaType | undefined
* }} params - function parameters inside an object
*
* @returns {Promise<FunctionReturn>} - Response object
*/
async function handleSocialDb({ social_id, email, social_platform, payload, res, invitation, supEmail, additionalFields, dbSchema }) {
const tableSchema = dbSchema?.tables.find((tb) => tb?.tableName === "users");
try {
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
let existingSocialIdUser = await varDatabaseDbHandler({
database: database ? database : "datasquirel",
queryString: `SELECT * FROM users WHERE social_id = ? AND social_login='1' AND social_platform = ? `,
queryValuesArray: [social_id.toString(), social_platform],
});
if (existingSocialIdUser && existingSocialIdUser[0]) {
return await loginSocialUser({
user: existingSocialIdUser[0],
social_platform,
res,
invitation,
database,
additionalFields,
});
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const finalEmail = email ? email : supEmail ? supEmail : null;
if (!finalEmail) {
return {
success: false,
user: null,
msg: "No Email Present",
social_id,
social_platform,
payload,
};
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
let existingEmailOnly = await varDatabaseDbHandler({
database: database ? database : "datasquirel",
queryString: `SELECT * FROM users WHERE email='${finalEmail}'`,
});
if (existingEmailOnly && existingEmailOnly[0]) {
return {
success: false,
user: null,
msg: "This Email is already taken",
alert: true,
};
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const foundUser = await varDatabaseDbHandler({
database: database ? database : "datasquirel",
queryString: `SELECT * FROM users WHERE email='${finalEmail}' AND social_login='1' AND social_platform='${social_platform}' AND social_id='${social_id}'`,
});
if (foundUser && foundUser[0]) {
return await loginSocialUser({
user: payload,
social_platform,
res,
invitation,
database,
additionalFields,
});
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
const socialHashedPassword = await encrypt({
data: social_id.toString(),
encryptionKey,
encryptionSalt,
});
const data = {
social_login: "1",
verification_status: supEmail ? "0" : "1",
password: socialHashedPassword,
};
Object.keys(payload).forEach((key) => {
data[key] = payload[key];
});
const newUser = await addDbEntry({
dbFullName: database ? database : "datasquirel",
tableName: "users",
duplicateColumnName: "email",
duplicateColumnValue: finalEmail,
data: {
...data,
email: finalEmail,
},
encryptionKey,
encryptionSalt,
tableSchema,
});
if (newUser?.insertId) {
const newUserQueried = await varDatabaseDbHandler({
database: database ? database : "datasquirel",
queryString: `SELECT * FROM users WHERE id='${newUser.insertId}'`,
});
if (!newUserQueried || !newUserQueried[0])
return {
success: false,
user: null,
msg: "User Insertion Failed!",
};
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
if (supEmail && database?.match(/^datasquirel$/)) {
/**
* Send email Verification
*
* @description Send verification email to newly created agent
*/
let generatedToken = encrypt({
data: JSON.stringify({
id: newUser.insertId,
email: supEmail,
dateCode: Date.now(),
}),
encryptionKey,
encryptionSalt,
});
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
return await loginSocialUser({
user: newUserQueried[0],
social_platform,
res,
invitation,
database,
additionalFields,
});
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
} else {
console.log("Social User Failed to insert in 'handleSocialDb.js' backend function =>", newUser);
return {
success: false,
user: null,
msg: "Social User Failed to insert in 'handleSocialDb.js' backend function => ",
newUser: newUser,
};
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
} catch (error) {
console.log("ERROR in 'handleSocialDb.js' backend function =>", error.message);
return {
success: false,
user: null,
error: error.message,
};
// serverError({
// component: "/functions/backend/social-login/handleSocialDb.js - main-catch-error",
// message: error.message,
// user: { first_name, last_name },
// });
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* Function to login social user
* ==============================================================================
* @description This function logs in the user after 'handleSocialDb' function finishes
* the user creation or confirmation process
*
* @async
*
* @param {object} params - function parameters inside an object
* @param {{
* first_name: string,
* last_name: string,
* email: string,
* social_id: string|number,
* }} params.user - user object
* @param {string} params.social_platform - Whether its "google" or "facebook" or "github"
* @param {http.ServerResponse} params.res - Https response object
* @param {object} params.invitation - A query object if user was invited
* @param {string|null} params.database - Target Database
* @param {object} [params.additionalFields] - Additional fields to be added to the user payload
*
* @returns {Promise<{
* success: boolean,
* user: { id: number, first_name: string, last_name: string } | null
* msg?: string
* }>}
*/
async function loginSocialUser({ user, social_platform, res, invitation, database, additionalFields }) {
const foundUser = await varDatabaseDbHandler({
database: database ? database : "datasquirel",
queryString: `SELECT * FROM users WHERE email='${user.email}' AND social_id='${user.social_id}' AND social_platform='${social_platform}'`,
});
let csrfKey = Math.random().toString(36).substring(2) + "-" + Math.random().toString(36).substring(2);
if (!foundUser?.[0]) {
return {
success: false,
user: null,
msg: "User Not Found",
};
}
let userPayload = {
id: foundUser[0].id,
type: foundUser[0].type || "",
stripe_id: foundUser[0].stripe_id || "",
first_name: foundUser[0].first_name,
last_name: foundUser[0].last_name,
username: foundUser[0].username,
email: foundUser[0].email,
social_id: foundUser[0].social_id,
image: foundUser[0].image,
image_thumbnail: foundUser[0].image_thumbnail,
verification_status: foundUser[0].verification_status,
social_login: foundUser[0].social_login,
social_platform: foundUser[0].social_platform,
csrf_k: csrfKey,
logged_in_status: true,
date: Date.now(),
};
if (additionalFields && Object.keys(additionalFields).length > 0) {
Object.keys(additionalFields).forEach((key) => {
userPayload[key] = foundUser[0][key];
});
}
let encryptedPayload = encrypt({
data: JSON.stringify(userPayload),
encryptionKey,
encryptionSalt,
});
if (res?.setHeader) {
res.setHeader("Set-Cookie", [`datasquirelAuthKey=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`, `csrf=${csrfKey};samesite=strict;path=/;HttpOnly=true`]);
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
return {
success: true,
user: userPayload,
};
}
module.exports = handleSocialDb;
+107
View File
@@ -0,0 +1,107 @@
// @ts-check
/**
* Imports
* ==============================================================================
*/
const https = require("https");
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* Main Function
* ==============================================================================
* @param {{
* url?: string,
* method: string,
* hostname: string,
* path?: string,
* href?: string,
* headers?: object,
* body?: object,
* }} params - params
*/
function httpsRequest({ url, method, hostname, path, href, headers, body }) {
const reqPayloadString = body ? JSON.stringify(body) : null;
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
let requestOptions = {
method: method,
hostname: hostname,
port: 443,
headers: {},
};
if (path) requestOptions.path = path;
if (href) requestOptions.href = href;
if (headers) requestOptions.headers = headers;
if (body) {
requestOptions.headers["Content-Type"] = "application/json";
requestOptions.headers["Content-Length"] = Buffer.from(reqPayloadString || "").length;
}
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
return new Promise((res, rej) => {
const httpsRequest = https.request(
/* ====== Request Options object ====== */
// @ts-ignore
url ? url : requestOptions,
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
/* ====== Callback function ====== */
(response) => {
var str = "";
// ## another chunk of data has been received, so append it to `str`
response.on("data", function (chunk) {
str += chunk;
});
// ## the whole response has been received, so we just print it out here
response.on("end", function () {
res(str);
});
response.on("error", (error) => {
console.log("HTTP response error =>", error.message);
});
}
);
if (body) httpsRequest.write(reqPayloadString);
httpsRequest.on("error", (error) => {
console.log("HTTPS request ERROR =>", error);
});
httpsRequest.end();
////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////
});
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
module.exports = httpsRequest;