@opengis/fastify-table
Version:
core-plugins
121 lines (120 loc) • 4.74 kB
JavaScript
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;