// @ts-check const http = require("http"); const decrypt = require("../package-shared/functions/dsql/decrypt"); const getAuthCookieNames = require("../package-shared/functions/backend/cookies/get-auth-cookie-names"); const { checkAuthFile, } = require("../package-shared/functions/backend/auth/write-auth-files"); const parseCookies = require("../package-shared/utils/backend/parseCookies"); 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 * ============================================================================== * @description This Function takes in a request object and returns a user object * with the user's data * * @param {Object} params - Arg * @param {http.IncomingMessage & Object} [params.request] - Http request object * @param {http.IncomingMessage & Object} [params.req] - Http request object * @param {string} [params.cookieString] * @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 (slug) * @param {string | number} [params.dsqlUserId] - alt env: DSQL_API_USER_ID * @param {number} [params.expiry] - Expiry time in milliseconds * @param {string} [params.csrfHeaderName] - Optional. CSRF Header Name * @param {boolean} [params.csrfHeaderIsValue] - If the csrf value is the name of the request http header * * @returns { import("../package-shared/types").AuthenticatedUser } */ function userAuth({ request, req, encryptionKey, encryptionSalt, level, database, dsqlUserId, encryptedUserString, expiry = weekInMilliseconds, cookieString, csrfHeaderIsValue, csrfHeaderName, }) { try { const finalRequest = req || request; const finalEncryptionKey = encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD; const finalEncryptionSalt = encryptionSalt || process.env.DSQL_ENCRYPTION_SALT; const cookies = parseCookies({ request: finalRequest, cookieString, }); 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 = encryptedUserString ? encryptedUserString : cookies[authKeyName]; const csrf = cookies[csrfName]; /** * Grab the payload * * @description Grab the payload */ let userPayloadJSON = decrypt({ encryptedString: key, encryptionKey: finalEncryptionKey, encryptionSalt: finalEncryptionSalt, }); /** * Grab the payload * * @description Grab the payload */ if (!userPayloadJSON) { return { success: false, payload: null, msg: "Couldn't Decrypt cookie", }; } /** * Grab the payload * * @description Grab the payload */ /** @type {import("../package-shared/types").DATASQUIREL_LoggedInUser} */ let userObject = JSON.parse(userPayloadJSON); if (!userObject.csrf_k) { return { success: false, payload: null, msg: "No CSRF_K in decrypted payload", }; } if (!checkAuthFile(userObject.csrf_k)) { return { success: false, payload: null, msg: "Auth file doesn't exist", }; } /** * Grab the payload * * @description Grab the payload */ if (level?.match(/deep/i) && finalRequest) { if ( csrfHeaderName && finalRequest.headers[csrfHeaderName] !== userObject.csrf_k ) { return { success: false, payload: null, msg: "CSRF_K mismatch", }; } const targetCsrfHeaderKey = Object.keys(finalRequest.headers) .map((k) => k.replace(/[^a-zA-Z0-9\-]/g, "")) .find((k) => k == userObject.csrf_k); if (csrfHeaderIsValue && !targetCsrfHeaderKey) { return { success: false, payload: null, msg: "CSRF_K Header Key mismatch", }; } } 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 * * @description Return User Object */ return { success: true, payload: userObject, }; } catch (/** @type {any} */ error) { /** * Return User Object * * @description Return User Object */ return { success: false, payload: null, msg: error.message, }; } } module.exports = userAuth;