UNPKG

@opengis/fastify-table

Version:

core-plugins

121 lines (120 loc) 4.74 kB
import crypto from "node:crypto"; import qrcode from "qrcode"; import { authenticator } from "otplib"; import config from "../../../../../../config.js"; const TYPE = "TOTP"; const getOTP = (id, secret) => authenticator.keyuri(id.toString(), config.auth?.["2fa"]?.prefix || "SOFTPRO", secret); const enableSecret = async ({ uid, pg }) => { await pg.query("update admin.users_social_auth set enabled=true where uid = $1 and social_auth_type = $2", [uid, TYPE]); }; const deleteSecret = async ({ uid, pg }) => { await pg.query("delete from admin.users_social_auth where uid=$1 and social_auth_type = $2", [uid, TYPE]); }; const getSecret = async ({ uid, pg }) => { const { social_auth_code: secret, enabled, recoveryCodes, } = await pg .query(`select social_auth_code, enabled, social_auth_obj->'codesArray' as "recoveryCodes" from admin.users_social_auth where uid = $1 and social_auth_type = $2`, [uid, TYPE]) .then((el) => el.rows?.[0] || {}); return { secret, enabled, recoveryCodes }; }; const addSecret = async ({ uid, secret, pg, recoveryCodes, otp }) => { await pg.query(`insert into admin.users_social_auth(uid, social_auth_code, social_auth_type, social_auth_obj, social_auth_url, enabled) values($1, $2, $3, $4::json, $5, false)`, [uid, secret, TYPE, { codesArray: recoveryCodes }, otp]); }; const updateSecret = async ({ uid, pg, secret, recoveryCodes, otp }) => { const result = await pg .query(`update admin.users_social_auth set social_auth_code=$3, social_auth_obj=$4::json, social_auth_url=$5 where uid = $1 and social_auth_type = $2`, [uid, TYPE, secret, { codesArray: recoveryCodes }, otp]) .then((el) => el.rows?.[0] || {}); return result; }; // return a new secret until it's enabled const generate = async ({ uid, pg }) => { const { enabled } = await getSecret({ uid, pg }); if (enabled) return { enabled }; const secret = authenticator.generateSecret(); // console.log('secret', secret, 'length', secret.length, 'token', authenticator.generate(secret), 'verified', authenticator.verify({ secret, token: authenticator.generate(secret) }) ); const recoveryCodes = [ crypto.randomUUID(), crypto.randomUUID(), crypto.randomUUID(), crypto.randomUUID(), ]; const userData = await pg .query(`select social_auth_id as code, coalesce(login,email) as login, email from admin.users where uid=$1`, [uid]) .then((el) => el.rows?.[0] || {}); const { sufix } = config.auth?.["2fa"] || {}; if (sufix && !userData[sufix]) { console.warn("⚠️ 2fa prefix not found at userData"); } const otp = getOTP((sufix ? userData[sufix] : null) || userData.login || userData.code || uid, secret); const qrCodeAsImageSource = await qrcode.toDataURL(otp); // no entry in db if (enabled === undefined) { await addSecret({ uid, secret, pg, recoveryCodes, otp, }); } else { await updateSecret({ uid, secret, pg, recoveryCodes, otp, }); } return { qr: qrCodeAsImageSource, key: secret, otp, recoveryCodes, }; }; const verify = async ({ uid, code: token, pg }) => { const { secret, enabled, recoveryCodes } = await getSecret({ uid, pg }); // console.debug('secret', secret, 'enabled', enabled, 'verification', 'token', authenticator.generate(secret), authenticator.verify({ token: authenticator.generate(secret), secret })); if (!secret) { throw new Error("Включіть двофакторну аутентифікацію"); } const isValid = authenticator.verify({ token, secret }) || recoveryCodes.reduce((result, recoveryCode) => result || recoveryCode === token, false); if (!isValid) { throw new Error("Невірний код"); } return { enabled, recoveryCodes }; }; // checks if code is correct // delete an entry from db to disable 2fa // set the enable flag in db to activate 2fa const toggle = async ({ uid, code, pg, enable }) => { const { enabled, recoveryCodes } = await verify({ uid, code, pg, }); if (enabled === enable) { throw new Error("Вже знаходиться у даному стані"); } if (enable) { await enableSecret({ uid, pg, }); return recoveryCodes; } await deleteSecret({ uid, pg, }); return "Відключено"; }; export { generate, verify, toggle, getSecret, enableSecret, deleteSecret }; export default null;