will-core
Version:
core module
325 lines (324 loc) • 15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwoFactorHandler = void 0;
const uuid_1 = require("uuid");
const will_db_1 = require("will-db");
const will_sql_1 = require("will-sql");
const will_api_1 = require("will-api");
const will_util_1 = require("will-util");
const SchemeHandler_1 = require("./SchemeHandler");
const VerifyError_1 = require("../models/VerifyError");
const node_2fa_1 = require("node-2fa");
class TwoFactorHandler extends SchemeHandler_1.SchemeHandler {
constructor() {
super(...arguments);
this.model = { name: "tuserfactor", alias: { privateAlias: this.section } };
this.handlers = [{ name: "get" }, { name: "generate" }, { name: "verify" }, { name: "confirm" }];
}
async generate(context) {
return this.callFunctional(context, { operate: "generate", raw: false }, this.doGenerate);
}
async verify(context) {
return this.callFunctional(context, { operate: "verify", raw: false }, this.doVerify);
}
async confirm(context) {
return this.callFunctional(context, { operate: "verify", raw: false }, this.doConfirm);
}
createFactorInfo(data) {
let email = (data?.email && data.email.trim().length > 0) ? data?.email : "";
let factorissuer = (data?.issuer && data.issuer.trim().length > 0) ? data?.issuer : "Smart2FA";
let secret = (0, node_2fa_1.generateSecret)({ name: factorissuer, account: email });
this.factorInfo = {
factorconfig: false, factorfound: false,
factorverify: data?.factorverify || false,
issuer: factorissuer,
userid: (data?.userid && data.userid.trim().length > 0) ? data?.userid : "",
factorid: (data?.factorid && data.factorid.trim().length > 0) ? data?.factorid : (0, uuid_1.v4)(),
factorflag: (data?.factorflag && data.factorflag.trim().length > 0) ? data?.factorflag : "0",
factorkey: secret.secret,
factorurl: secret.uri,
factorremark: secret.qr,
email: email,
};
return this.factorInfo;
}
async genterateFactor(info) {
let token = (0, node_2fa_1.generateToken)(info.factorkey);
if (token) {
return { valid: true, token: token.token };
}
return { valid: false, token: "" };
}
async verifyFactor(info, factorcode) {
let result = (0, node_2fa_1.verifyToken)(info.factorkey, factorcode);
if (result) {
return { verified: true, delta: result.delta };
}
return { verified: false, delta: -1 };
}
composeFactorUri(info) {
return "otpauth://totp/" + encodeURIComponent(info.name + ":" + info.account) + "?secret=" + encodeURIComponent(info.secret) + "&issuer=" + encodeURIComponent(info.name);
}
async doGenerate(context, model) {
let info = await this.doGet(context, model);
if (info && info.factorfound) {
return this.genterateFactor(info);
}
return this.recordNotFound();
}
async doGenerating(context, db, findByUser = false) {
await this.validateRequireFields(context, this.model, "generate");
let info = await this.getFactorInfo(db, context.params.factorid, findByUser, context);
if (info && info.factorfound) {
return this.genterateFactor(info);
}
return { valid: false, token: "" };
}
async doVerify(context, model) {
let vi = this.validateParameters(context.params, "factorcode");
if (!vi.valid) {
return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061));
}
let info = await this.doGet(context, model);
if (info && info.factorfound) {
return this.verifyFactor(info, context.params.factorcode);
}
return this.recordNotFound();
}
async doVerifying(context, db, findByUser = false) {
await this.validateRequireFields(context, this.model, will_db_1.Operation.VERIFY);
let info = await this.getFactorInfo(db, context.params.factorid, findByUser, context);
if (info && info.factorfound) {
return this.verifyFactor(info, context.params.factorcode);
}
return { verified: false, delta: -1 };
}
async doGet(context, model) {
await this.validateRequireFields(context, model, will_db_1.Operation.GET);
let db = this.getPrivateConnector(model);
try {
let info = await this.getFactorInfo(db, context.params.factorid, false, context);
return await this.createCipherData(context, will_db_1.Operation.GET, info);
}
catch (ex) {
this.logger.error(this.constructor.name, ex);
return Promise.reject(this.getDBError(ex));
}
finally {
if (db)
db.close();
}
}
async doConfirm(context, model) {
await this.validateRequireFields(context, model, will_db_1.Operation.UPDATE);
let db = this.getPrivateConnector(model);
try {
let rs = await this.doConfirming(context, db);
return await this.createCipherData(context, will_db_1.Operation.UPDATE, rs);
}
catch (ex) {
this.logger.error(this.constructor.name, ex);
return Promise.reject(this.getDBError(ex));
}
finally {
if (db)
db.close();
}
}
async doConfirming(context, db) {
let userToken = await this.getUserTokenInfo(context, true);
await this.updateUserFactor(db, userToken?.useruuid, context.params.factorcode, context);
return await this.confirmFactor(db, context.params.factorid, userToken?.userid, context);
}
validateRequireFields(context, model, action) {
let vi = this.validateParameters(context.params, "factorid");
if (!vi.valid) {
return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061));
}
return Promise.resolve(vi);
}
async doRetrieve(context, model) {
await this.validateRequireFields(context, model, will_db_1.Operation.RETRIEVE);
let db = this.getPrivateConnector(model);
try {
let rs = await this.doRetrieving(context, db);
return await this.createCipherData(context, will_db_1.Operation.RETRIEVE, rs);
}
catch (ex) {
this.logger.error(this.constructor.name, ex);
return Promise.reject(this.getDBError(ex));
}
finally {
if (db)
db.close();
}
}
async doRetrieving(context, db) {
return await this.performRetrieving(db, context.params.factorid, false, context);
}
async performRetrieving(db, id, findByUser = true, context) {
if (!id || id.trim().length == 0)
return this.createRecordSet();
let knsql = new will_sql_1.KnSQL();
knsql.append("select * from tuserfactor ");
if (findByUser) {
knsql.append("where userid=?userid ");
knsql.set("userid", id);
}
else {
knsql.append("where factorid=?factorid ");
knsql.set("factorid", id);
}
this.logger.debug(this.constructor.name + ".performRetrieving", knsql);
let rs = await knsql.executeQuery(db, context);
return this.createRecordSet(rs);
}
async getFactorInfo(db, id, findByUser = true, context) {
let cfg = await this.getConfigure(db, context);
let rs = await this.performRetrieving(db, id, findByUser, context);
if (rs && rs.rows.length > 0) {
let row = rs.rows[0];
let factorurl = this.composeFactorUri({ name: row.issuer, account: row.email, secret: row.factorkey });
return {
factorfound: true,
factorconfig: cfg?.factorconfig || false,
factorverify: cfg?.factorverify || false,
issuer: row.issuer || cfg?.issuer,
userid: row.userid,
factorid: row.factorid, factorkey: row.factorkey,
factorflag: row.factorflag, email: row.email,
factorurl: factorurl, factorremark: row.factorremark
};
}
return { ...cfg, userid: "", email: "", factorfound: false, factorid: "", factorkey: "", factorflag: "" };
}
async getConfigure(db, context) {
let result = { factorconfig: false, factorverify: false, issuer: "" };
let knsql = new will_sql_1.KnSQL();
knsql.append("select * from tconfig where category='2FA' ");
let rs = await knsql.executeQuery(db, context);
if (rs && rs.rows.length > 0) {
for (let row of rs.rows) {
if (will_util_1.Utilities.equalsIgnoreCase("FACTORVERIFY", row.colname)) {
result.factorverify = will_util_1.Utilities.equalsIgnoreCase("true", row.colvalue);
result.factorconfig = true;
}
else if (will_util_1.Utilities.equalsIgnoreCase("FACTORISSUER", row.colname)) {
result.issuer = row.colvalue;
result.factorconfig = true;
}
}
}
return result;
}
async doCreate(context, model) {
let vi = this.validateParameters(context.params, "userid", "email");
if (!vi.valid) {
return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061));
}
await this.doCreating(context, model);
return await this.createCipherData(context, will_db_1.Operation.CREATE, this.factorInfo);
}
async doClear(context, model) {
await this.validateRequireFields(context, model, will_db_1.Operation.CLEAR);
let rs = await this.doClearing(context, model);
return await this.createCipherData(context, will_db_1.Operation.CLEAR, rs);
}
async doInsert(context, model) {
return this.doCreate(context, model);
}
async doUpdate(context, model) {
return this.doConfirm(context, model);
}
async doRemove(context, model) {
return this.doClear(context, model);
}
async performCreating(context, model, db) {
let info = await this.performCreateFactor(context, db, context.params);
return { rows: [info], columns: null };
}
async performCreateFactor(context, db, data) {
let userToken = await this.getUserTokenInfo(context, true);
let info = this.createFactorInfo(data);
await this.createFactor(db, info, userToken?.userid);
return info;
}
async performClearing(context, model, db) {
return this.deleteFactor(db, context.params.factorid);
}
async createFactor(db, info, edituser, context) {
let now = will_util_1.Utilities.now();
let knsql = new will_sql_1.KnSQL();
knsql.append("insert into tuserfactor(factorid,userid,factorkey,email,issuer,factorflag,factorurl,");
knsql.append("createdate,createtime,createtranstime,editdate,edittime,edituser,factorremark) ");
knsql.append("values(?factorid,?userid,?factorkey,?email,?issuer,?factorflag,?factorurl,");
knsql.append("?createdate,?createtime,?createtranstime,?editdate,?edittime,?edituser,?factorremark) ");
knsql.set("factorid", info.factorid);
knsql.set("userid", info.userid);
knsql.set("factorkey", info.factorkey);
knsql.set("email", info.email);
knsql.set("issuer", info.issuer);
knsql.set("factorflag", info.factorflag);
knsql.set("factorurl", info.factorurl);
knsql.set("createdate", now, "DATE");
knsql.set("createtime", now, "TIME");
knsql.set("createtranstime", now.getTime());
knsql.set("editdate", now, "DATE");
knsql.set("edittime", now, "TIME");
knsql.set("edituser", edituser);
knsql.set("factorremark", info.factorremark);
let rs = await knsql.executeUpdate(db, context);
return this.createRecordSet(rs);
}
async deleteFactor(db, factorid, context) {
await this.moveToHistory(db, factorid, context);
return this.removeFactor(db, factorid, context);
}
async removeFactor(db, factorid, context) {
let knsql = new will_sql_1.KnSQL();
knsql.append("delete from tuserfactor ");
knsql.append("where factorid = ?factorid ");
knsql.set("factorid", factorid);
let rs = await knsql.executeUpdate(db, context);
return this.createRecordSet(rs);
}
async moveToHistory(db, factorid, context) {
let knsql = new will_sql_1.KnSQL();
knsql.append("insert into tuserfactorhistory ");
knsql.append("select * from tuserfactor ");
knsql.append("where factorid = ?factorid ");
knsql.set("factorid", factorid);
let rs = await knsql.executeUpdate(db, context);
return this.createRecordSet(rs);
}
async confirmFactor(db, factorid, edituser, context) {
let now = will_util_1.Utilities.now();
let knsql = new will_sql_1.KnSQL();
knsql.append("update tuserfactor set factorflag = '1' ");
knsql.append(", confirmdate=?confirmdate, confirmtime=?confirmtime ");
knsql.append(", confirmtranstime=?confirmtranstime ");
knsql.append(", editdate=?editdate, edittime=?edittime, edituser=?edituser ");
knsql.append("where factorid = ?factorid ");
knsql.set("factorid", factorid);
knsql.set("confirmdate", now, "DATE");
knsql.set("confirmtime", now, "TIME");
knsql.set("confirmtranstime", now.getTime());
knsql.set("editdate", now, "DATE");
knsql.set("edittime", now, "TIME");
knsql.set("edituser", edituser);
let rs = await knsql.executeUpdate(db, context);
return this.createRecordSet(rs);
}
async updateUserFactor(db, useruuid, factorcode, context) {
if (!useruuid || useruuid.trim().length == 0)
return this.createRecordSet();
let knsql = new will_sql_1.KnSQL();
knsql.append("update tusertoken set factorcode = ?factorcode ");
knsql.append("where useruuid = ?useruuid ");
knsql.set("factorcode", factorcode);
knsql.set("useruuid", useruuid);
let rs = await knsql.executeUpdate(db, context);
return this.createRecordSet(rs);
}
}
exports.TwoFactorHandler = TwoFactorHandler;